I’m planning to add an “official” command line tool for developers to interact with our API. The tool is basically just a glorified curl client, interacting with the API via HTTPS exclusively. To make this convenient for developers, it should allow them to authorize the CLI using their existing user account, using an
Therefore, the user interaction should work like the following:
- The user executes
- The CLI uses OpenID discovery to locate our OAuth endpoints, and builds an authorization URL. The
client_idis hard-coded in the CLI source code; to prevent having to include the
client_secret, we use PKCE to generate a local challenge.
- The CLI generates a random, unique hash as a device identifier. It then opens a URL in the user’s browser with the device identifier and full authorization URL as query parameters:
This endpoint stores the device ID in a session cookie, then redirects to the authorization URL.
- The user signs in and authorizes the CLI to access their account on their behalf.
- The OAuth server redirects them to the redirect URI, which points to another endpoint on our application server:
This endpoint stores the authorization code to be fetched by the CLI instance later on together with the device identifier (step 3) from the cookie.
- After opening the authorization link in the user’s browser, the CLI regularly polls an endpoint on the application server, passing their device identifier, for the authorization code:
As soon as the code is available, it exchanges it for an access token as an ordinary web application would.
- If the authorization code isn’t fetched within 60 seconds, the TTL expires and it is purged.
This process is defined pretty well by OAuth, apart from the signaling mechanism between our server and the CLI application instance running on the developer’s computer.
Considering we use PKCE, even leaking the authorization code somehow should not be a problem, as it is useless without the code verifier, so I think this process should be as secure as the OAuth spec allows. Are there any flaws I’ve overlooked? Is there a better or more secure way to implement this?