OAuth PKCE Error — Fix
OAuth Error Response
{"error": "invalid_grant", "error_description": "PKCE verification failed: code_verifier does not match code_challenge"}The code_verifier you sent in the token exchange does not match the code_challenge you sent in the authorization request. They must be cryptographically linked — the challenge is the SHA-256 hash of the verifier.
Common causes
- Verifier not stored before the redirect — regenerated on the callback page
- Wrong base64 encoding — must be base64url (no padding, - and _ instead of + and /)
- Wrong hash method — must be SHA-256 (S256), not plain
- Verifier stored in localStorage but cleared (private browsing, storage policies)
Correct PKCE implementation
// Generate verifier and store BEFORE redirecting
const verifier = generateVerifier();
sessionStorage.setItem('pkce_verifier', verifier); // store here
const challenge = await sha256Base64url(verifier);
// redirect to auth with code_challenge=challenge
// On callback — retrieve the STORED verifier
const verifier = sessionStorage.getItem('pkce_verifier'); // NOT regenerated
await exchangeCode(code, verifier);
function generateVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
async function sha256Base64url(str) {
const data = new TextEncoder().encode(str);
const digest = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
Debug your OAuth PKCE error → OAuthFixer