Skip to content

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.

  • A running Autentico instance
  • A registered public client with authorization_code grant type and a known redirect URI
  • A user account to authenticate with
// 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);
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.

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.

Terminal window
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"
}

Fetch the JWKS from /.well-known/jwks.json and verify the ID token signature using the public key. Check:

  • iss matches https://auth.example.com/oauth2
  • aud matches your client_id
  • exp is in the future

The sub claim is the user’s unique ID. Use it to identify the user in your application.

When the access token expires (expires_in seconds), use the refresh token:

Terminal window
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.