The guide
A "flow" (or "grant") is the way an application obtains its tokens. There are several because applications are very different: a website, a mobile app, a TV, a service without a user… do not have the same constraints or the same secrets to protect.
See also: the flows as sequence diagrams · advanced features explained (PAR, JAR, JARM, DPoP, CIBA) · the OAuth and OpenID Connect timeline
Everything else exists for historical reasons or specific needs. Refresh Token is not a sign-in flow: it is a complement to keep the session alive.
OAuth 2.0 (2012) had to serve both servers able to keep a secret and browser applications that cannot. Hence implicit for SPAs, password for “trusted” applications, and code for servers. Over the years it became clear that tokens in the URL leaked (implicit) and that handing the password to the application defeated the whole point (password). OAuth 2.1 and current security recommendations have therefore brought everything back to code + PKCE, keeping client credentials and device for their own cases. OIDC added the hybrid flow to get the identity straight away.
Use it when: a person signs in to a website (with or without a server), a SPA, a mobile or desktop application. With a server able to keep a secret, add the client_secret; without one (SPA, mobile), PKCE is enough.
Why it exists: the browser only receives a single-use, short-lived code; the tokens go directly from the application’s server to the OP (“back channel”). PKCE ties the code to whoever asked for it, so an intercepted code is useless to anyone else.
Pitfalls: forgetting PKCE (now required even for confidential clients), not checking state and nonce, accepting overly broad redirect_uris, tokens in localStorage for a SPA.
Walkthrough:
code_verifier (a random secret), state, nonce, and computes code_challenge = SHA-256(code_verifier)./authorize?response_type=code&code_challenge=…&state=…&nonce=….redirect_uri?code=…&state=…./token with the code and the code_verifier (and its secret if it has one).iss, aud and nonce.Use it when: the application needs the identity immediately (before the code exchange), for example for server-side session validation or because an older library (ASP.NET, Java EE) configures it by default.
Why it exists: OIDC wanted to let you read the identity straight away while keeping the code to obtain tokens safely. The c_hash and at_hash in the ID token bind the code and the access token to that identity.
Pitfalls: tokens in the URL fragment (visible to history and scripts), unchecked c_hash/at_hash, and a forgotten mandatory nonce. Prefer the plain code flow unless something forces you not to.
Walkthrough:
/authorize?response_type=code id_token&nonce=… (or code token, code id_token token).#code=…&id_token=…&state=… in the fragment (response_mode=fragment by default) or by POST (form_post).c_hash.Use it when: almost never for new work. You meet it in old SPAs, or to check that an application really forbids it.
Why it exists: before browsers handled cross-origin calls (CORS) and PKCE, a SPA could not call /token, so it was given the tokens directly.
Pitfalls: the token travels in the URL: history, proxy logs, the Referer header, extensions. No refresh token, no binding to the caller. Removed from OAuth 2.1.
Walkthrough:
/authorize?response_type=id_token token&nonce=….#id_token=…&access_token=…&state=….Use it when: a service, a scheduled job or a daemon calls an API in its own name: synchronisation, nightly job, microservices talking to each other.
Why it exists: many API calls have no person behind them. The client proves its identity (secret, or better: a signed assertion) and receives an access token limited to its scopes.
Pitfalls: no ID token or refresh token (that is normal). Never put this secret in a distributed application (mobile, SPA). Grant the bare minimum of scopes and rotate the secret.
Walkthrough:
POST /token with grant_type=client_credentials, scope=… and the client authentication (Basic secret or in the body).Authorization: Bearer ….Use it when: you want short-lived access tokens (a few minutes) while keeping the person signed in for a long time. Requested with the offline_access scope.
Why it exists: the longer a token lives, the worse its theft. The refresh token stays on the application side, away from the APIs, and is only used to renew.
Pitfalls: a stolen refresh token means prolonged access: safe storage, rotation (a new one on each use, the old one becomes invalid) and reuse detection.
Walkthrough:
POST /token with grant_type=refresh_token&refresh_token=….Use it when: TV, console, connected object, printer, command-line tool: the device has no browser or convenient keyboard.
Why it exists: typing a password with a remote control is painful and risky. A short code is displayed; the person enters it on their phone or computer, where they authenticate normally.
Pitfalls: short-lived, single-use code, no polling too fast (slow_down), and attempt limits so a code can’t be guessed. Phishing is possible: show clearly what the person is authorising.
Walkthrough:
/device_authorization and receives device_code, user_code and the verification address./token with the device_code. First authorization_pending, then the tokens.Use it when: to be avoided. Sometimes tolerated for a migration from an old system or automated tests in a controlled environment.
Why it exists: a transition for applications that already collected passwords, without changing their interface.
Pitfalls: the application sees the password: exactly what OIDC tries to avoid. No multi-factor authentication, no SSO, easier phishing. Removed from OAuth 2.1.
Walkthrough:
POST /token with grant_type=password&username=…&password=….All the details in pictures: sequence diagrams.
Test each flow. ID-Kit’s test provider can play all these flows, with simulated errors; the test application runs them against your real provider.