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:

  1. Generate a Code Verifier: Create a random string that will be used as the code_verifier.
  2. Generate a Code Challenge: Hash the code_verifier using SHA-256 and then base64url encode the result to create the code_challenge.
  3. Authorization Request: Send the code_challenge and the method used to generate it (S256) in the authorization request.
  4. Authorization Response: The user authorizes the application, and the authorization server redirects back to the redirect URI with an authorization code.
  5. Token Request: Send the authorization code and the code_verifier to the token endpoint to exchange for an access token.
  6. Token Response: The authorization server verifies the code_verifier against the code_challenge and issues an access token if they match.

Quick Answer

To implement PKCE in Auth0 for SPAs, follow these steps:

  1. Generate a code_verifier.
  2. Create a code_challenge by hashing the code_verifier with SHA-256 and base64url encoding it.
  3. Initiate the authorization request with the code_challenge and code_challenge_method=S256.
  4. 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.

⚠️ Warning: Using `plain` as the code challenge method is not recommended due to security vulnerabilities.

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.

🚨 Security Alert: Ensure the code verifier is securely stored and not exposed.

Missing Parameters

Omitting required parameters like code_challenge or code_challenge_method in the authorization request will result in errors.

💡 Key Point: Always include `code_challenge` and `code_challenge_method=S256` in the authorization request.

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

ApproachProsConsUse When
PKCENo client secret neededSlightly more complexSPAs, mobile apps
Client SecretSimpler implementationClient secret exposure riskConfidential 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 (code for 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

graph TD A[SPA] --> B[Generate Code Verifier] B --> C[Generate Code Challenge] C --> D[Authorization Request] D --> E[Auth0 Authorization Server] E --> F[Authorization Response] F --> G[SPA] G --> H[Exchange Authorization Code] H --> I[Auth0 Token Endpoint] I --> J[Token Response] J --> K[SPA]

Terminal Output

Terminal
$ curl -X POST https://your-auth0-domain/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d 'grant_type=authorization_code' \ -d 'client_id=YOUR_CLIENT_ID' \ -d 'code=AUTHORIZATION_CODE' \ -d 'redirect_uri=https://yourapp.com/callback' \ -d 'code_verifier=CODE_VERIFIER' { "access_token": "eyJ...", "id_token": "eyJ...", "token_type": "Bearer", "expires_in": 86400 }

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_verifier is correctly generated and stored.
  • The code_challenge is accurately created from the code_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.