PKCE for Single-Page Applications (SPAs)
How to implement OAuth 2.0 Authorization Code + PKCE in a single-page application. Step-by-step with code examples for code_verifier and code_challenge generation.
Detailed Explanation
PKCE for Single-Page Applications
PKCE (Proof Key for Code Exchange, pronounced "pixy") is an extension to the Authorization Code flow that makes it safe for public clients — applications that cannot securely store a client_secret, such as browser-based SPAs, mobile apps, and desktop applications.
Why SPAs Need PKCE
SPAs run entirely in the browser, which means:
- They cannot securely store a
client_secret(any secret in JavaScript is accessible to users) - Authorization codes can be intercepted by malicious browser extensions or through open redirectors
- The Implicit flow (the old alternative) exposes tokens directly in the URL
PKCE solves the authorization code interception problem by adding a cryptographic proof that the client that started the flow is the same one exchanging the code.
How PKCE Works
1. Generate a code_verifier — a random string (43-128 characters):
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const code_verifier = base64url(array);
2. Derive the code_challenge using SHA-256:
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest(
"SHA-256",
encoder.encode(code_verifier)
);
const code_challenge = base64url(new Uint8Array(digest));
3. Authorization Request — include the challenge:
GET /authorize?response_type=code
&client_id=spa-client
&redirect_uri=https://app.example.com/callback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&scope=openid profile
&state=random-state
4. Token Exchange — include the original verifier:
POST /token
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://app.example.com/callback
&client_id=spa-client
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The authorization server computes SHA256(code_verifier) and compares it to the stored code_challenge. If they match, the token is issued.
Best Practices
- Always use
S256(neverplain) - Store the code_verifier in sessionStorage (cleared on tab close)
- Use a new code_verifier for every authorization request
- Combine PKCE with the
stateparameter for defense in depth
Use Case
A React, Vue, or Angular SPA that authenticates users via an OAuth 2.0 provider (e.g., Auth0, Okta, Azure AD). The app generates a PKCE pair before redirecting to the login page and uses the code_verifier during the token exchange.