Skip to main content

PKCE Explained

Proof Key for Code Exchange (PKCE) - Enhanced OAuth security.


What is PKCE?

PKCE (pronounced "pixy") is a security extension to OAuth 2.0 that protects authorization code interception attacks.

Defined in RFC 7636.


The Problem

Without PKCE, authorization codes can be intercepted:

1. User starts login
2. App redirects to auth server with code request
3. User authenticates
4. Auth server returns code to redirect URI

⚠️ Attacker intercepts code

5. Attacker exchanges code for tokens
6. Attacker gains access

The Solution

PKCE adds a secret known only to the original client:

1. App generates random code_verifier
2. App calculates code_challenge = sha256(code_verifier)
3. App stores code_verifier locally
4. App sends code_challenge to auth server
5. User authenticates
6. Auth server returns authorization code
7. App exchanges code + code_verifier for tokens
8. Auth server verifies sha256(code_verifier) == code_challenge
9. Tokens issued only if verification passes

If an attacker intercepts the code, they can't exchange it without the code_verifier.


How PKCE Works

Step 1: Generate Code Verifier

A random, high-entropy string:

function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64url(array);
}

// Example output:
// "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

Step 2: Create Code Challenge

SHA-256 hash of the verifier:

async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64url(new Uint8Array(hash));
}

// Example output:
// "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Step 3: Authorization Request

Include challenge in the request:

GET /authorize?
response_type=code
&client_id=xxx
&redirect_uri=https://app.com/callback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=random123

Step 4: Token Exchange

Include verifier in exchange:

POST /oauth/token

grant_type=authorization_code
&code=received_code
&redirect_uri=https://app.com/callback
&client_id=xxx
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Step 5: Server Verification

Server calculates sha256(code_verifier) and compares to stored code_challenge:

sha256("dBjftJeZ...") == "E9Melhoa2O..."

MATCH ✓

Issue tokens

Challenge Methods

MethodDescriptionRecommended
S256SHA-256 hash✅ Yes
plainNo hashing❌ No

Always use S256 (SHA-256).


SDK Implementation

The SyAuth SDK handles PKCE automatically:

// All of this happens automatically:
// 1. Generate code_verifier
// 2. Calculate code_challenge
// 3. Store verifier in secure cookie
// 4. Send challenge in auth request
// 5. Retrieve verifier for token exchange

loginWithRedirect(); // PKCE is automatic

Security Benefits

AttackWithout PKCEWith PKCE
Code interceptionVulnerableProtected
Man-in-the-middleVulnerableProtected
Malicious appVulnerableProtected
Replay attacksPossiblePrevented

Why Not Just Client Secret?

Client secrets don't work for:

  • Public clients (mobile apps, SPAs) - can't securely store secrets
  • Native apps - secrets can be extracted from binary

PKCE works for ALL client types.


Common Issues

"Invalid code_verifier"

Causes:

  • Verifier lost between requests
  • Different client making token request
  • Verifier corrupted in storage

Solution:

  • Ensure verifier is stored in secure, persistent storage
  • Same client instance must complete the flow

"Code already used"

Authorization codes are one-time use. You cannot retry the token exchange.


Next Steps