PKCE Flow Walkthrough
This walkthrough shows the complete PKCE flow using raw HTTP requests — useful for debugging, testing, or understanding what your OIDC library does under the hood.
Prerequisites
- A running Autentico instance
- A registered public client with
authorization_codegrant type and a known redirect URI - A user account to authenticate with
Step 1: Generate PKCE parameters
// In a browser or Node.js environment
const codeVerifier = generateCodeVerifier(); // 43-128 random URL-safe chars
async function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
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(/=/g, '');
}
const verifier = await generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
Step 2: Build the authorization URL
GET https://auth.example.com/oauth2/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://app.example.com/callback&
scope=openid+profile+email&
code_challenge=BASE64URL_SHA256_OF_VERIFIER&
code_challenge_method=S256&
state=RANDOM_STATE_VALUE
Redirect the user's browser to this URL. Autentico shows the login page.
Step 3: User authenticates
The user enters their credentials on the Autentico login page. If MFA is enabled, they complete the MFA challenge. Autentico then redirects to:
https://app.example.com/callback?code=AUTH_CODE&state=RANDOM_STATE_VALUE
Verify state matches what you sent to prevent CSRF.
Step 4: Exchange the code for tokens
curl -X POST https://auth.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://app.example.com/callback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code_verifier=YOUR_CODE_VERIFIER"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "openid profile email"
}
Step 5: Verify the ID token
Fetch the JWKS from /oauth2/.well-known/jwks.json and verify the ID token signature using the public key. Check:
issmatcheshttps://auth.example.com/oauth2audmatches yourclient_idexpis in the future
The sub claim is the user's unique ID. Use it to identify the user in your application.
Step 6: Refresh the access token
When the access token expires (expires_in seconds), use the refresh token:
curl -X POST https://auth.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "client_id=YOUR_CLIENT_ID"
Store the new refresh token from the response — the old one is invalidated.