PKCE, or Proof Key for Code Exchange, is a method used to secure the authorization code flow in OAuth 2.0 by adding a cryptographic challenge to prevent authorization code interception attacks. This is particularly crucial for Single Page Applications (SPAs) where client secrets cannot be safely stored.
What is PKCE?
PKCE is an extension to the standard OAuth 2.0 Authorization Code flow. It introduces two new parameters: code_challenge and code_verifier. The code_verifier is a high-entropy cryptographic random string that is used to generate the code_challenge. During the token exchange, the code_verifier is sent to the authorization server to verify that the request is coming from the same party that initiated the authorization request.
Why use PKCE for SPAs?
SPAs run entirely in the browser and cannot securely store client secrets. Without PKCE, an attacker could intercept the authorization code and exchange it for an access token. PKCE mitigates this risk by ensuring that only the original client that initiated the authorization request can exchange the authorization code for a token.
How does PKCE work?
PKCE works by adding a cryptographic challenge to the authorization code flow. Here’s a step-by-step breakdown:
- Generate a Code Verifier: Create a random string that will be used as the
code_verifier. - Generate a Code Challenge: Hash the
code_verifierusing SHA-256 and then base64url encode the result to create thecode_challenge. - Authorization Request: Send the
code_challengeand the method used to generate it (S256) in the authorization request. - Authorization Response: The user authorizes the application, and the authorization server redirects back to the redirect URI with an authorization code.
- Token Request: Send the authorization code and the
code_verifierto the token endpoint to exchange for an access token. - Token Response: The authorization server verifies the
code_verifieragainst thecode_challengeand issues an access token if they match.
Quick Answer
To implement PKCE in Auth0 for SPAs, follow these steps:
- Generate a
code_verifier. - Create a
code_challengeby hashing thecode_verifierwith SHA-256 and base64url encoding it. - Initiate the authorization request with the
code_challengeandcode_challenge_method=S256. - Exchange the authorization code for an access token using the
code_verifier.
Implementing PKCE in Auth0 for SPAs
Let’s dive into the implementation details. We’ll use JavaScript for this example.
Step 1: Generate the Code Verifier
The code_verifier should be a random string between 43 and 128 characters long.
function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, array)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
const codeVerifier = generateCodeVerifier();
Step 2: Generate the Code Challenge
Hash the code_verifier using SHA-256 and base64url encode it.
async function generateCodeChallenge(codeVerifier) {
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashBase64 = btoa(String.fromCharCode.apply(null, hashArray)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
return hashBase64;
}
const codeChallenge = await generateCodeChallenge(codeVerifier);
Step 3: Authorization Request
Initiate the authorization request with the code_challenge and code_challenge_method.
const clientId = 'YOUR_CLIENT_ID';
const domain = 'YOUR_AUTH0_DOMAIN';
const redirectUri = encodeURIComponent('https://yourapp.com/callback');
const responseType = 'code';
const scope = 'openid profile email';
const state = 'random_state_string';
const url = `${domain}/authorize?response_type=${responseType}&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
window.location.href = url;
Step 4: Handle the Authorization Response
When the user authorizes the application, they will be redirected to the specified redirectUri with an authorization code.
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const returnedState = params.get('state');
if (state !== returnedState) {
throw new Error('Invalid state');
}
// Store codeVerifier securely for later use
sessionStorage.setItem('codeVerifier', codeVerifier);
Step 5: Token Request
Exchange the authorization code for an access token using the code_verifier.
const codeVerifierFromStorage = sessionStorage.getItem('codeVerifier');
const tokenUrl = `${domain}/oauth/token`;
const tokenResponse = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
code: code,
redirect_uri: redirectUri,
code_verifier: codeVerifierFromStorage
})
});
const tokenData = await tokenResponse.json();
console.log(tokenData); // Contains access_token, id_token, etc.
Common Pitfalls
Incorrect Code Challenge Method
Using the wrong code_challenge_method can lead to errors. Always use S256 for SHA-256 hashing.
Mismatched Code Verifier
If the code_verifier sent during the token request does not match the one used to generate the code_challenge, the token exchange will fail.
Missing Parameters
Omitting required parameters like code_challenge or code_challenge_method in the authorization request will result in errors.
Security Considerations
Secure Storage of Code Verifier
The code_verifier must be securely stored and not exposed. In a browser environment, use sessionStorage or localStorage with appropriate security measures.
Avoid Predictable Values
Never use predictable values for the code_verifier. Always generate a random string using a secure random number generator.
Validate Responses
Always validate the responses from the authorization server. Check for errors and ensure the state parameter matches to prevent CSRF attacks.
Comparison of PKCE vs. Client Secret
| Approach | Pros | Cons | Use When |
|---|---|---|---|
| PKCE | No client secret needed | Slightly more complex | SPAs, mobile apps |
| Client Secret | Simpler implementation | Client secret exposure risk | Confidential clients |
Quick Reference
📋 Quick Reference
generateCodeVerifier()- Generates a secure random string for the code verifier.generateCodeChallenge(codeVerifier)- Creates a code challenge from the code verifier.fetch(tokenUrl, ...)- Sends a POST request to the token endpoint to exchange the authorization code for an access token.
Expanding on the Implementation
🔍 Click to see detailed explanation
Detailed Explanation
Code Verifier Generation
The codeVerifier is a random string that must be securely generated. The example uses window.crypto.getRandomValues for cryptographic randomness.
Code Challenge Generation
The codeChallenge is derived from the codeVerifier using SHA-256 hashing and base64url encoding. This ensures that the challenge is unique and secure.
Authorization Request
The authorization request includes several parameters:
response_type: Specifies the response type (codefor authorization code flow).client_id: Your Auth0 client ID.redirect_uri: The URI to which the authorization server will redirect after authorization.scope: The requested scopes.state: A random string to prevent CSRF attacks.code_challenge: The generated code challenge.code_challenge_method: The method used to generate the code challenge (S256).
Token Request
The token request exchanges the authorization code for an access token. It includes:
grant_type: Specifies the grant type (authorization_code).client_id: Your Auth0 client ID.code: The authorization code received from the authorization server.redirect_uri: The same redirect URI used in the authorization request.code_verifier: The original code verifier used to generate the code challenge.
Step-by-Step Guide
Configure the client
Set up your Auth0 client to allow the authorization code flow with PKCE.Generate the code verifier
Create a secure random string for the code verifier.Create the code challenge
Hash the code verifier and base64url encode it to create the code challenge.Initiate the authorization request
Send the authorization request with the code challenge and method.Handle the authorization response
Process the authorization response and extract the authorization code.Exchange the authorization code
Send the authorization code and code verifier to the token endpoint.Architecture Flow
Terminal Output
Key Takeaways
🎯 Key Takeaways
- PKCE adds a cryptographic challenge to the authorization code flow to prevent interception attacks.
- Use PKCE in SPAs where client secrets cannot be securely stored.
- Generate a secure `code_verifier` and derive the `code_challenge` using SHA-256 and base64url encoding.
- Store the `code_verifier` securely and use it during the token exchange.
Troubleshooting
Error: Invalid Code Verifier
If you receive an error indicating an invalid code verifier, ensure that:
- The
code_verifieris correctly generated and stored. - The
code_challengeis accurately created from thecode_verifier.
Error: Mismatched State
If the state parameter does not match, check that:
- The state parameter is correctly generated and passed in the authorization request.
- The state parameter is validated in the authorization response.
Error: Unauthorized Client
If you encounter an unauthorized client error, verify that:
- The client ID is correct.
- The redirect URI matches the one configured in Auth0.
Conclusion
Implementing PKCE in Auth0 for SPAs enhances security by preventing authorization code interception attacks. By following the steps outlined in this guide, you can ensure that your application securely handles the authorization code flow. That’s it. Simple, secure, works.

