OAuth 2.0 flows
Published
When a photo printing app asks to read your Google Photos, it should never see your Google password. You go to Google, Google asks "let PrintKaro see your photos?", you say yes, and PrintKaro gets a token that can read photos and do nothing else. That is OAuth 2.0: a way to hand an app limited access to something of yours, without handing over your password.
A flow (the spec calls it a grant) is the sequence of redirects and requests that ends with the app holding that token. There used to be five. In 2026 you need three, and the interview question is almost always "which one, and why".
A YouTube app on a smart TV needs to sign you in. Typing a password with a TV remote is miserable and the TV has no proper browser. How would you design the sign in?
The four roles#
- Resource owner: you. The photos are yours.
- Client: the app that wants access, PrintKaro.
- Authorization server: the one that asks for your consent and issues tokens, Google's accounts service.
- Resource server: the API holding the data, the Google Photos API. It accepts the token.
And two kinds of client, which decides everything below. A confidential client runs on a server and can keep a secret. A public client runs on a device the user controls (a browser SPA, a mobile app, a CLI) and cannot, because anyone can unpack the app and read it.
Flow 1: authorization code with PKCE#
This is the default for any app with a user and a browser: web apps, single page apps, mobile apps. Two steps make it safe. The token never travels through the browser's address bar: the browser only carries a short lived, single use code, and the app swaps the code for tokens over a direct back channel. And PKCE (Proof Key for Code Exchange, said "pixie") proves that whoever swaps the code is the same app that started the flow.
Why the code and not the token directly? An old flow called implicit did exactly that:
the token came back in the URL fragment. URLs end up in browser history, in logs and in
Referer headers, and malicious apps on a phone could register the same custom URL scheme
and catch the redirect. PKCE fixes the stolen code problem even for public clients, which is
why OAuth 2.1 drops implicit entirely and requires PKCE for every authorization code flow.
Flow 2: client credentials#
No user at all. A nightly billing job needs to call the invoices API. There is nobody to ask for consent, so the service authenticates as itself, with a client id and secret (or better, a signed JWT or a client certificate), and receives a token that represents the service.
curl -X POST https://auth.acme.in/oauth/token \
-d grant_type=client_credentials \
-d client_id=billing-job \
-d client_secret=$BILLING_SECRET \
-d scope=invoices:readThis is the machine to machine flow. It is only for confidential clients, since it depends on a secret. Never ship it inside a mobile app.
Flow 3: device authorization#
The smart TV problem. The device cannot show a decent browser and has a terrible keyboard, but you have a phone in your hand. The TV asks for a device code, shows you a short code and a URL, and polls while you approve on your phone.
The same flow signs in command line tools (gh auth login, az login) and anything else
with no browser. Its weakness is phishing: an attacker can start a device flow and send you
the code with a convincing message. Show the device name on the approval screen, and keep the
codes short lived.
The ones that are gone#
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Implicit grant | One fewer round trip, back when browsers could not make cross origin POSTs. | Token in the URL, leaks through history and referrers, no way to tie it to the client. | Removed in OAuth 2.1. Use authorization code with PKCE. |
| Resource owner password grant | The app collects the username and password and swaps them for a token. | Defeats the point of OAuth: the app sees the password, and MFA and SSO cannot work. | Removed in OAuth 2.1. At most for migrating a legacy first party app. |
| Refresh token grant | Swaps a long lived refresh token for a new short lived access token. | A stolen refresh token is valuable, so rotate it and detect reuse. | Still current. Used alongside the other flows. See access vs refresh tokens. |
Picking a flow#
In the interview#
"What is the state parameter for?" A random value tied to the user's session, checked on the
callback, so an attacker cannot make you complete a login they started (login CSRF). "What
is a scope?" A named permission the user consented to, carried in the token, checked by the
API. "Does OAuth tell you who the user is?" No, that is OIDC's ID token.
Checkpoint#
1. What does PKCE protect against?
2. A backend job with no user needs to call an internal API. Which flow?
3. Why did OAuth 2.1 remove the implicit flow?
- OAuth gives an app limited access without your password. It is authorization, not login.
- Authorization code + PKCE for anything with a user and a browser, public or confidential.
- Client credentials for machine to machine. Device code for TVs and CLIs.
- Implicit and password grants are removed in OAuth 2.1.
statestops forged callbacks, PKCE stops stolen codes, scopes limit the token.
OAuth 2.0 lets an app get limited access to a user's data without the user's password. The app redirects the user to an authorization server, the user consents to specific scopes, and the app gets an access token the resource server accepts. For any app with a user and a browser, including SPAs and mobile apps, I use the authorization code flow with PKCE: the browser only carries a single use code, and the app swaps it for tokens on a back channel while proving, with a verifier whose hash it sent at the start, that it started the flow. For machine to machine calls with no user I use client credentials. For devices without a usable browser, like TVs and CLIs, I use the device flow, where the user approves on their phone. Implicit and password grants are removed in OAuth 2.1 because they leak tokens or expose passwords. And OAuth alone is not login; for identity I add OpenID Connect.