Authorization code flow

OAuth, OIDC & JWT · Essentials · 15 min

It is the flow almost every interactive app should use.

The authorization code grant is the default flow for any app with a user:

  • The client redirects the browser to the AS's authorization endpoint with response_type=code, its client_id, a pre-registered redirect_uri, the requested scope and a random state.
  • The user logs in at the AS and approves the requested access.
  • The AS redirects back to redirect_uri?code=...&state=.... The code is short-lived (often under a minute) and single-use.
  • The client checks that state matches what it stored, then POSTs the code to the token endpoint (a back-channel call, not through the browser), authenticating itself if it is a confidential client.
  • The AS returns JSON with an access_token, token_type (Bearer), expires_in, often a refresh_token, and with OIDC an id_token.

Why the two steps? The front channel (browser redirects) is visible to the user's browser, history and extensions; the token travels only on the direct, authenticated back channel.

state protects against CSRF on the callback: an attacker cannot make your app accept their code. Redirect URIs must be registered and matched exactly by the AS, so codes cannot be sent to an attacker's site.

Every client, including SPAs and mobile apps, should also use PKCE with this flow, next lesson.

Example

# 1. Browser is redirected to the authorization endpoint
https://auth.example.com/authorize?response_type=code
  &client_id=web-app
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=openid%20profile%20orders.read
  &state=af0ifjsldkj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

# 2. After login/consent the AS redirects back:
# https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj

# 3. Back channel: exchange the code for tokens
curl -s https://auth.example.com/token \
  -u "web-app:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=SplxlOBeZQQYbYS6WxSbIA \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
# {"access_token":"...","token_type":"Bearer","expires_in":3600,"refresh_token":"..."}

Remember

  • Front channel returns a short-lived code; back channel swaps it for tokens
  • state must round-trip and match to block CSRF
  • Redirect URIs are pre-registered and matched exactly
  • Use PKCE with every authorization code flow

Watch out for

  • Skipping the state check on the callback, allowing login CSRF.

Check yourself

  1. Why are tokens not returned directly in the browser redirect?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.rfc-editor.org/rfc/rfc6749#section-4.1