Authorization Code Flow with PKCE
PKCE (Proof Key for Code Exchange) is a security extension designed for public clients that cannot securely store a client secret. PKCE is required for all public clients (SPAs, mobile apps).
How PKCE works
PKCE adds an extra layer of security using a dynamically generated secret:
- Code Verifier: A random string (43–128 characters) generated by your application
- Code Challenge: The SHA256 hash of the code verifier, sent with the authorization request
- Verification: The token endpoint verifies that the code verifier matches the original challenge
This prevents authorization code interception attacks, because an attacker would need the code verifier to exchange the code for tokens.
Step 1: Generate the code verifier and challenge
The code verifier must be a cryptographically random string using the characters: A-Z, a-z, 0-9, -, ., _, ~
JavaScript example:
// Generate a code verifier (43-128 characters)
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// Generate a code challenge (SHA256 hash of the verifier)
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
// Base64 URL encoding (without padding)
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Securely store the codeVerifier (sessionStorage) for later use
sessionStorage.setItem('code_verifier', codeVerifier);
Example values:
Code Verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Code Challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
Step 2: Build the authorization URL with PKCE
Add the PKCE parameters to the standard authorization request:
https://your-sso-domain.com/connect/authorize?\
client_id=my-spa-app&\
redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback&\
response_type=code&\
scope=openid%20profile%20email&\
state=abc123xyz&\
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&\
code_challenge_method=S256
Additional PKCE parameters:
| Parameter | Required | Description |
|---|---|---|
code_challenge | Yes | SHA256 hash of the code verifier (base64url encoded) |
code_challenge_method | Yes | Must be S256 (SHA256) or plain (not recommended) |
Step 3: Exchange the code for tokens
When exchanging the code, include the original code verifier (NOT the challenge):
curl -X POST https://your-sso-domain.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=my-spa-app" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://myapp.com/callback" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
No client_secret is required for public clients. The code_verifier proves ownership of the authorization request.
Token request parameters (PKCE):
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be authorization_code |
client_id | Yes | Your application's client ID |
code | Yes | The authorization code from the callback |
redirect_uri | Yes | Must exactly match the original request |
code_verifier | Yes | The original code verifier (NOT the challenge) |
Common PKCE errors
| Error | Cause | Resolution |
|---|---|---|
invalid_grant with a "code_verifier" message | The code verifier doesn't match the challenge | Make sure you use the same verifier that was used to generate the challenge |
invalid_request | Missing code_challenge or code_verifier | Include both parameters in the appropriate requests |
| Challenge method not supported | Using an unsupported method | Use S256 (recommended) |