Skip to main content

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:

  1. Code Verifier: A random string (43–128 characters) generated by your application
  2. Code Challenge: The SHA256 hash of the code verifier, sent with the authorization request
  3. 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:

ParameterRequiredDescription
code_challengeYesSHA256 hash of the code verifier (base64url encoded)
code_challenge_methodYesMust 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 required

No client_secret is required for public clients. The code_verifier proves ownership of the authorization request.

Token request parameters (PKCE):

ParameterRequiredDescription
grant_typeYesMust be authorization_code
client_idYesYour application's client ID
codeYesThe authorization code from the callback
redirect_uriYesMust exactly match the original request
code_verifierYesThe original code verifier (NOT the challenge)

Common PKCE errors​

ErrorCauseResolution
invalid_grant with a "code_verifier" messageThe code verifier doesn't match the challengeMake sure you use the same verifier that was used to generate the challenge
invalid_requestMissing code_challenge or code_verifierInclude both parameters in the appropriate requests
Challenge method not supportedUsing an unsupported methodUse S256 (recommended)