When building applications that need to authenticate users via OAuth 2.0, especially using the Authorization Code flow, you might encounter the term code_verifier. If you’re like me, you might have wondered, “What is this code_verifier and why is it important?” This post will demystify code_verifier, explain its role in Proof Key for Code Exchange (PKCE), and provide practical examples to help you implement it correctly.

The Problem: Authorization Code Flow Vulnerability

The Authorization Code flow in OAuth 2.0 is widely used because it balances security and usability. However, it has a known vulnerability: if an attacker intercepts the authorization code, they can exchange it for an access token. This is particularly problematic in public clients, like single-page applications (SPAs) and mobile apps, where you can’t store a client secret securely.

Enter PKCE, which addresses this issue by adding an additional layer of security to the Authorization Code flow. Let’s dive into how it works.

What is PKCE?

Proof Key for Code Exchange (PKCE) is an extension to the Authorization Code flow designed to protect against authorization code interception attacks. It requires public clients to generate a cryptographic key pair and send a code challenge derived from the public key to the authorization server. When exchanging the authorization code for an access token, the client proves possession of the private key by sending the original code verifier.

How Does code_verifier Work?

The code_verifier is a random string generated by the client. This string is then transformed into a code_challenge using a cryptographic hash function (usually SHA-256). The code_challenge is sent to the authorization server along with the authorization request. When the client exchanges the authorization code for an access token, it sends the code_verifier.

Here’s a step-by-step breakdown:

  1. Generate code_verifier: Create a random string.
  2. Create code_challenge: Hash the code_verifier using SHA-256.
  3. Send Authorization Request: Include code_challenge and code_challenge_method (e.g., S256).
  4. Receive Authorization Code: After user consent, the authorization server redirects back to the client with an authorization code.
  5. Exchange Authorization Code: Send the authorization code and code_verifier to the authorization server to get the access token.

Let’s see this in action with some code.

Practical Example: Implementing PKCE in OAuth 2.0

Step 1: Generate code_verifier and code_challenge

// Generate a random string for code_verifier
function generateRandomString(length) {
    let result = '';
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const charactersLength = characters.length;
    for (let i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}

const code_verifier = generateRandomString(128); // 128 characters recommended
console.log('code_verifier:', code_verifier);

// Create code_challenge using SHA-256
async function generateCodeChallenge(codeVerifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(codeVerifier);
    const digest = await crypto.subtle.digest('SHA-256', data);
    return btoa(String.fromCharCode(...new Uint8Array(digest)))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
}

const code_challenge = await generateCodeChallenge(code_verifier);
console.log('code_challenge:', code_challenge);

Step 2: Send Authorization Request

const clientId = 'YOUR_CLIENT_ID';
const redirectUri = 'https://yourapp.com/callback';
const scope = 'openid profile email';

const authUrl = `https://authorization-server.com/authorize?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}&code_challenge=${encodeURIComponent(code_challenge)}&code_challenge_method=S256`;

window.location.href = authUrl;

Step 3: Receive Authorization Code

After the user grants permission, the authorization server redirects to your callback URL with an authorization code:

https://yourapp.com/callback?code=AUTHORIZATION_CODE_HERE

Step 4: Exchange Authorization Code for Access Token

const authorizationCode = new URLSearchParams(window.location.search).get('code');

const tokenEndpoint = 'https://authorization-server.com/token';

fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: authorizationCode,
        redirect_uri: redirectUri,
        client_id: clientId,
        code_verifier: code_verifier // Include the code_verifier here
    })
})
.then(response => response.json())
.then(data => {
    console.log('Access Token:', data.access_token);
})
.catch(error => console.error('Error:', error));

Common Mistakes When Implementing PKCE

  1. Using Weak Randomness for code_verifier: Always generate a sufficiently long and random code_verifier (at least 128 characters).
  2. Incorrect Encoding of code_challenge: Ensure the code_challenge is Base64 URL-encoded without padding.
  3. Omitting code_challenge_method: Always specify the method used to create the code_challenge (e.g., S256 for SHA-256).
  4. Reusing code_verifier: Each authorization request should have a unique code_verifier.
  5. Not Validating Responses: Always validate the responses from the authorization server to prevent man-in-the-middle attacks.

Security Benefits of PKCE

  1. Prevents Authorization Code Interception: Even if an attacker intercepts the authorization code, they cannot exchange it for an access token without the code_verifier.
  2. No Client Secret Required: Public clients do not need to store a client secret, reducing the risk of exposure.
  3. Enhanced Security for SPA and Mobile Apps: Ideal for environments where storing secrets securely is challenging.

Real-World Scenario: Debugging PKCE Issues

I’ve debugged this 100+ times, and here’s a common scenario:

Problem: You receive an error when exchanging the authorization code for an access token.

Error Message:

{
    "error": "invalid_grant",
    "error_description": "Invalid code verifier"
}

Solution: Double-check the following:

  • Correct code_verifier: Ensure the code_verifier used in the token request matches the one used in the authorization request.
  • Proper Encoding: Verify that the code_challenge was correctly Base64 URL-encoded without padding.
  • Consistent Method: Make sure the code_challenge_method specified in the authorization request matches the method used to create the code_challenge.

This saved me 3 hours last week. Always verify these details carefully.

Conclusion

Implementing PKCE in your OAuth 2.0 Authorization Code flow is crucial for securing public clients. By generating a code_verifier, creating a code_challenge, and ensuring correct usage during the authorization and token exchange processes, you can significantly enhance the security of your application.

Go ahead and implement PKCE in your projects. It’s simple, secure, works.