OAuth 2.0 + PKCE Walkthrough
Modern OAuth 2.1 flow with PKCE explained for web and mobile apps — with a real code example.
OAuth 2.1 with PKCE (Proof Key for Code Exchange) is the recommended flow for any public client — SPAs, mobile apps and desktop apps — because it removes the need for a client secret while still preventing authorization-code interception attacks.
The flow at a glance
- Client generates a random
code_verifier(43–128 URL-safe chars). - Client derives
code_challenge = BASE64URL(SHA256(code_verifier)). - Client redirects the user to
/authorizewithcode_challengeandcode_challenge_method=S256. - Auth server returns an authorization code to the redirect URI.
- Client exchanges the code at
/token, sending the originalcode_verifier. - Auth server verifies the SHA-256 hash matches and issues an access token.
Working JavaScript example
function base64url(buf) {
return btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function pkcePair() {
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
return { verifier, challenge: base64url(digest) };
}
const { verifier, challenge } = await pkcePair();
sessionStorage.setItem("pkce_verifier", verifier);
const params = new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
code_challenge: challenge,
code_challenge_method: "S256",
scope: "openid profile email",
state: crypto.randomUUID(),
});
window.location.href = `${AUTH_URL}?${params}`;
Common mistakes
- Storing
code_verifierinlocalStorage— usesessionStorageor an httpOnly cookie so an XSS on another tab can't steal it. - Using SHA-1 or the plain method — always use
S256. - Skipping the
stateparameter — it prevents CSRF on the callback. - Not rotating refresh tokens — 2.1 mandates rotation for public clients.
When PKCE is NOT enough
PKCE prevents code interception, not token theft. If the browser gets XSSed after login, the attacker still gets the access token. Pair PKCE with:
- Short-lived access tokens (5–15 minutes).
- Rotating refresh tokens.
- Strict CSP that disallows inline scripts.