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
ParamPurpose
client_idIdentifies your app to the auth server
response_type=codeAsks for the Authorization Code Flow specifically
scopeWhat you’re asking for (openid triggers OIDC)
redirect_uriWhere to send the user back; must exactly match a registered URI
stateAnti-CSRF token — see Social Login Security
nonceAnti-replay for ID Token — see Social Login Security
code_challenge / code_challenge_methodPKCE

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_code

Response (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_uri between authorization and token requests → token request rejected. Must be byte-identical.
  • Not validating state on 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.

See Also