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:

Parameter Description
client_id Your application’s client identifier
redirect_uri Where to send the user after authorization (must be pre-registered)
response_type Must be code
scope Space-separated list of scopes (include openid for OIDC)

See the PKCE documentation for details on generating these values.

Parameter Description
code_challenge Base64url-encoded SHA256 hash of the code verifier
code_challenge_method Must be S256
Parameter Description
state Recommended. Random string to prevent CSRF attacks
nonce Recommended. Random string included in the ID token to prevent replay attacks
prompt Controls authentication and consent behavior (none, login, consent). See Prompt parameter
max_age Maximum acceptable time in seconds since the user last authenticated
ui_locales Preferred languages for the login and consent screens (space-separated BCP 47 tags, e.g. pt-BR en). See Localization
acr_values Requested authentication level. See ACR and AMR
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.

Parameter Required Description
grant_type Yes Must be authorization_code
code Yes The authorization code received
redirect_uri Yes Must match the original request
client_id Yes Your application’s client identifier
client_secret Conditional Required for confidential clients
code_verifier Conditional Required 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
  • login_required - User authentication is required (typically with prompt=none)
  • consent_required - User consent is required (typically with prompt=none)
  • interaction_required - User interaction is required, such as ACR step-up (typically with prompt=none)

A client that registered itself receives none of these. Goiabada withholds its error redirects and shows the user a page instead, so an authorization that fails never returns to the client at all.

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)