A developer platform ships a CLI that must act as the signed-in user on machines that may have no browser at all - SSH sessions on servers and containers in continuous integration. Which OAuth flow should the CLI use?
Show the full answer Hide the answer
The deciding property
Whether a browser can be opened on the machine where the command runs. Every other consideration - token storage, refresh, scopes - is common to all four options. Only one of them works when the answer is no.
The device authorisation grant, specified in RFC 8628 (2019), exists for exactly this: the CLI asks the authorisation server for a short user code, prints it with a URL, and polls while the user completes the sign-in on a device that does have a browser. Nothing secret ever passes through the constrained machine.
Why not the others, and when each would be right
- Authorization code with PKCE on a loopback redirect is the correct answer for a native or desktop application on a machine with a browser, and it is better than the device grant there - fewer steps, no code to copy. It is also what the CLI should use when it detects a usable browser. It simply cannot complete on a headless host, which is the constraint in the stem. Ship both and choose at runtime.
- Client credentials with a secret in the binary fails twice. A secret shipped to users is not a secret - it is extractable from any installation - and client credentials authenticate the client, not the user, so every action would be attributed to the CLI rather than to a person. That destroys the audit trail, which for a developer platform is the thing auditors ask about.
- Resource owner password credentials means the CLI collects the user's password, which defeats federated sign-in, cannot carry multi-factor authentication, and trains users to type their password into tools. The grant was deprecated in the OAuth 2.0 security best-current-practice work and should not be used in new designs.
What would flip the decision
| If this changes | Choose | Because |
|---|---|---|
| The CLI is running in CI, not under a person | OIDC workload federation - no user flow at all | A pipeline is not a user; an exchanged workload token is short-lived and attributable to the job, with no stored credential |
| A browser is reliably available | Authorization code with PKCE on loopback | Fewer steps and no copying; the device grant's user code is a phishing-shaped interaction |
| The CLI needs to act for an organisation rather than a person | An installed application identity with scoped permissions | User tokens inherit everything that user can reach, which is far more than the task needs |
What it costs
The device grant's weakness is social: users are trained to type a code shown by one program into a page opened by another, which is the shape of a real phishing technique. Mitigate it by displaying the requesting client's name and the scopes on the confirmation page, keeping the code's lifetime short - typically 5 to 15 minutes - and rate-limiting the polling endpoint.
Common weak answers
- "Use client credentials; it is our own tool." Authenticates the tool, not the person, so every action is attributed to the CLI and the audit trail is gone.
- "Prompt for the password; we are first party." Defeats federated sign-in, cannot carry multi-factor authentication, and trains users to type passwords into tools.
- "Mint a long-lived personal access token and paste it in." Works, and replaces a managed grant with an invisible permanent credential in a shell history and a dotfile.
What it costs, continued
The deeper cost is the token the CLI then holds. A user token carries that user's whole reach. Store it in the platform keychain rather than a dotfile, scope it to what the CLI needs, give it an expiry with a refresh token, and make revocation visible to the user in account settings. For CI, the right answer is the first row of the table: do not let a pipeline hold a human's token at all.