Skip to content

Authorization Code Flow

The Authorization Code flow is the recommended OAuth2 flow for all applications that involve user authentication. It provides the best security by keeping tokens away from the browser’s address bar and supporting refresh tokens for long-lived sessions.

The authorization code flow involves two steps:

  1. Authorization: The user authenticates and grants permission, receiving an authorization code
  2. Token Exchange: The application exchanges the code for tokens at the token endpoint

Redirect the user to the authorization endpoint with the following parameters:

ParameterDescription
client_idYour application’s client identifier
redirect_uriWhere to send the user after authorization (must be pre-registered)
response_typeMust be code
scopeSpace-separated list of scopes (include openid for OIDC)

See the PKCE documentation for details on generating these values.

ParameterDescription
code_challengeBase64url-encoded SHA256 hash of the code verifier
code_challenge_methodMust be S256
ParameterDescription
stateRandom string to prevent CSRF attacks
nonceRandom string included in the ID token to prevent replay attacks
GET /auth/authorize?
client_id=my-app&
redirect_uri=https://my-app.com/callback&
response_type=code&
scope=openid profile email&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
state=abc123&
nonce=xyz789

After successful authentication and consent, the user is redirected back to your application:

https://my-app.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=abc123

Exchange the authorization code for tokens by making a POST request to the token endpoint.

ParameterRequiredDescription
grant_typeYesMust be authorization_code
codeYesThe authorization code received
redirect_uriYesMust match the original request
client_idYesYour application’s client identifier
client_secretConditionalRequired for confidential clients
code_verifierConditionalRequired if PKCE was used
Terminal window
curl -X POST https://auth.example.com/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://my-app.com/callback" \
-d "client_id=my-app" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email"
}

When the access token expires, use the refresh token to obtain a new one without requiring user interaction.

Terminal window
curl -X POST https://auth.example.com/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=8xLOxBtZp8" \
-d "client_id=my-app" \
-d "client_secret=my-secret"
  1. Create a client in the admin console (Clients → Add Client)
  2. Set the client type:
    • Public for SPAs, mobile apps, desktop apps
    • Confidential for server-side web apps
  3. Enable Authorization Code flow in OAuth2 flows settings
  4. Register your Redirect URIs
  5. Configure PKCE requirement (recommended: required)

PKCE can be configured at two levels:

  • Global: Settings → General → PKCE Required
  • Per-client: Clients → [Client] → OAuth2 Flows → PKCE

See PKCE documentation for detailed configuration options.

  1. Always use PKCE - Even for confidential clients, PKCE adds defense in depth
  2. Validate state parameter - Prevent CSRF attacks by verifying the state matches
  3. Validate nonce in ID token - Prevent replay attacks
  4. Use short-lived authorization codes - Codes should expire within minutes
  5. Store tokens securely - Never expose tokens in URLs or localStorage for sensitive apps
  6. Implement token refresh - Use refresh tokens instead of storing long-lived access tokens
  7. Use HTTPS everywhere - Never transmit tokens over unencrypted connections

Errors during authorization are returned as query parameters:

https://my-app.com/callback?error=access_denied&error_description=User+denied+consent&state=abc123

Common error codes:

  • invalid_request - Missing or invalid parameter
  • unauthorized_client - Client not authorized for this flow
  • access_denied - User denied the request
  • unsupported_response_type - Invalid response_type
  • invalid_scope - Invalid or unknown scope

Errors during token exchange return a JSON response with cache prevention headers per RFC 6749 Section 5.2:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}

For client authentication failures (HTTP 401), a WWW-Authenticate header is included when HTTP Basic authentication was used:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic

Common error codes:

  • invalid_grant - Code expired, already used, or invalid (HTTP 400)
  • invalid_client - Client authentication failed (HTTP 401)
  • invalid_request - Missing required parameter (HTTP 400)