Authorization Code Flow
The canonical OAuth 2.0 grant: the user’s browser receives a short-lived code, which the app’s server then exchanges for tokens. Browser never sees the tokens.
Why This Indirection Exists
The browser is not a safe place for secrets. Anything that lands in the URL or the page can leak (referrers, history, browser extensions, XSS). The authorization code is a single-use, short-lived intermediary that’s only useful when combined with a server-held client secret (or a PKCE verifier). Steal the code mid-flight and you still can’t trade it for tokens.
The Flow, Step by Step
sequenceDiagram participant U as User Browser participant C as Client App (server) participant AS as Authorization Server participant RS as Resource Server U->>C: 1. Click "Sign in with X" C-->>U: 2. 302 redirect with auth URL U->>AS: 3. GET /authorize?client_id&scope&state&nonce&redirect_uri AS-->>U: 4. Login + consent screen U->>AS: 5. Submit credentials, approve AS-->>U: 6. 302 to redirect_uri?code=AUTH_CODE&state=... U->>C: 7. GET /callback?code=... C->>AS: 8. POST /token (code, client_id, secret, redirect_uri) AS-->>C: 9. {access_token, id_token, refresh_token?} C->>RS: 10. GET /resource (Authorization: Bearer access_token) RS-->>C: 11. Protected data
The Authorization Request
GET https://accounts.google.com/o/oauth2/v2/auth
?client_id=YOUR_APP_ID
&response_type=code
&scope=openid%20email%20profile
&redirect_uri=https://yourapp.com/callback
&state=random_anti_forgery_token
&nonce=random_replay_protection
&code_challenge=BASE64URL(SHA256(verifier))
&code_challenge_method=S256| Param | Purpose |
|---|---|
client_id | Identifies your app to the auth server |
response_type=code | Asks for the Authorization Code Flow specifically |
scope | What you’re asking for (openid triggers OIDC) |
redirect_uri | Where to send the user back; must exactly match a registered URI |
state | Anti-CSRF token — see Social Login Security |
nonce | Anti-replay for ID Token — see Social Login Security |
code_challenge / code_challenge_method | PKCE |
The Token Exchange
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
code=AUTH_CODE_HERE
&client_id=YOUR_APP_ID
&client_secret=YOUR_SECRET # confidential clients only
&code_verifier=ORIGINAL_VERIFIER # PKCE: instead of/alongside secret
&redirect_uri=https://yourapp.com/callback
&grant_type=authorization_codeResponse (OIDC variant):
{
"access_token": "ya29.a0Af...",
"id_token": "eyJhbGciOi...",
"expires_in": 3599,
"refresh_token": "1//0g...",
"scope": "openid email profile",
"token_type": "Bearer"
}Why the Code Is Single-Use
The auth server invalidates the code on first redemption. If an attacker intercepts the code and races your server to redeem it, only one of you wins. Combined with PKCE, even a successful interception is useless without the original verifier.
Pitfalls
- Mismatched
redirect_uribetween authorization and token requests → token request rejected. Must be byte-identical. - Not validating
stateon callback → CSRF on the redirect. - Doing the token exchange from the browser → you’ve leaked the client_secret. The exchange is server-side or uses PKCE (public client).
- Re-using the code → second exchange returns an error and (in spec-compliant servers) revokes the issued tokens.