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
Section titled “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
Section titled “Step 1: Generate PKCE parameters”// In a browser or Node.js environmentconst 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
Section titled “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_VALUERedirect the user’s browser to this URL. Autentico shows the login page.
Step 3: User authenticates
Section titled “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_VALUEVerify state matches what you sent to prevent CSRF.
Step 4: Exchange the code for tokens
Section titled “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
Section titled “Step 5: Verify the ID token”Fetch the JWKS from /.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
Section titled “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.