Authorization Code Flow + PKCE
The Authorization Code flow is the recommended OAuth2 grant type for web applications, SPAs, and native apps. Autentico supports PKCE (RFC 7636) for public clients that cannot securely store a client secret.
Callback query parameters already present in the registered redirect_uri are preserved. The response URL encodes code and state as query values, so special characters in state round-trip unchanged, including when login requires MFA. Clients should decode query parameters with their URL library and compare the returned state with the original value.
Request objects passed through request or request_uri are unsupported. Nonempty values return request_not_supported or request_uri_not_supported, respectively, before login or SSO. See discovery metadata.
Authorization results use query parameters. A nonempty unsupported response_mode, including fragment or form_post, returns invalid_request before login or SSO. Discovery advertises only ["query"].
Flow overview
Section titled “Flow overview”sequenceDiagram
actor User
participant App as Relying Party
participant Browser
participant IdP as Autentico
App->>Browser: Redirect to /oauth2/authorize with PKCE params
Browser->>IdP: GET /oauth2/authorize
IdP->>Browser: Render login page
User->>Browser: Enter credentials
Browser->>IdP: POST /oauth2/login
Note over IdP: MFA challenge if enabled
IdP->>Browser: 302 → redirect_uri?code=AUTH_CODE&state=...
Browser->>App: Deliver AUTH_CODE
App->>IdP: POST /oauth2/token (code + code_verifier)
IdP->>App: { access_token, id_token, refresh_token }
Omitting code_challenge_method means plain (RFC 7636 §4.3). With the default S256 enforcement, that request returns invalid_request at /authorize. When enforcement is disabled for a legacy integration, an omitted method uses the verifier itself as the challenge, and the token exchange compares them directly.
Step 1: Generate PKCE parameters
Section titled “Step 1: Generate PKCE parameters”PKCE is required for public clients and recommended for all clients. Unsupported code_challenge_method values, or a method without a challenge, are rejected at /authorize with invalid_request before login or SSO code issuance. Use S256; plain is accepted only when S256 enforcement is disabled.
function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');}
async function generateCodeChallenge(verifier) { const data = new TextEncoder().encode(verifier); const digest = await crypto.subtle.digest('SHA-256', data); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');}
const codeVerifier = generateCodeVerifier();// Store codeVerifier for the token exchange stepsessionStorage.setItem('code_verifier', codeVerifier);const codeChallenge = await generateCodeChallenge(codeVerifier);Step 2: Authorization request
Section titled “Step 2: Authorization request”Redirect the user’s browser to the authorization endpoint:
GET /oauth2/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=openid profile email &state=RANDOM_STATE_VALUE &nonce=RANDOM_NONCE_VALUE &code_challenge=PKCE_CHALLENGE &code_challenge_method=S256| Parameter | Required | Description |
|---|---|---|
response_type |
Yes | Must be code; other values return unsupported_response_type, including on SSO |
client_id |
Yes | Your registered client ID |
response_mode |
Optional | Only query is supported; omission or an empty value uses query encoding |
redirect_uri |
Yes | Must exactly match a registered redirect URI |
scope |
Yes | Space-separated scopes. Must include openid for OIDC. |
state |
Recommended | Opaque value for CSRF protection. Returned unchanged. |
nonce |
Recommended | Opaque value embedded in the ID token to prevent replay attacks. |
code_challenge |
Required for public clients | For S256, the base64url-encoded SHA-256 hash of code_verifier; for plain, the verifier itself |
code_challenge_method |
Optional | S256 (recommended) or plain; omission means plain and is rejected when S256 enforcement is enabled |
Step 3: User authenticates
Section titled “Step 3: User authenticates”Autentico renders the login page. After successful authentication (including MFA if enabled), the user is redirected to your redirect_uri:
https://yourapp.com/callback ?code=AUTHORIZATION_CODE &state=RANDOM_STATE_VALUEVerify that state matches what you sent. The authorization code is short-lived (default 10 minutes) and single-use. Concurrent exchanges have one winner: marking the code used and saving its token/session pair happen in one transaction. A storage failure rolls back the exchange so it can be retried. Replaying a used code with valid client, redirect and PKCE proof rejects the request and revokes tokens linked to that browser session for the client, including refreshed tokens. Legacy codes without browser-session linkage use a broader user/client fallback for code and refresh grants.
Step 4: Token exchange
Section titled “Step 4: Token exchange”Exchange the authorization code for tokens at the token endpoint.
curl -X POST https://auth.example.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=YOUR_CLIENT_ID" \ -d "code=AUTHORIZATION_CODE" \ -d "redirect_uri=https://yourapp.com/callback" \ -d "code_verifier=YOUR_CODE_VERIFIER"curl -X POST https://auth.example.com/oauth2/token \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTHORIZATION_CODE" \ -d "redirect_uri=https://yourapp.com/callback"curl -X POST https://auth.example.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "code=AUTHORIZATION_CODE" \ -d "redirect_uri=https://yourapp.com/callback"Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 900, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."}The id_token is a JWT containing identity claims about the authenticated user. See Token Structure & Claims for details.
Keycloak-compatible path
Section titled “Keycloak-compatible path”Autentico also registers the Keycloak-compatible token endpoint:
POST /oauth2/protocol/openid-connect/tokenThis allows existing Keycloak client integrations to work by changing only the base URL.
The token endpoint rejects code_verifier when the authorization request had no code_challenge, including confidential clients, as required by RFC 9700 §4.8.2. Rejection returns invalid_grant without consuming the code. Confidential clients that omit PKCE continue exchanging codes without a verifier.