I’m building a custom single-page application that needs to authenticate users via the Authorization Code flow with PKCE. The goal is to avoid storing secrets in the browser. I’ve generated the code_verifier and code_challenge using SHA-256 as per the spec. The initial request to /oauth/token works fine, but the exchange fails. The API returns a 400 Bad Request with invalid_grant. The error message says the code verifier does not match. I’ve double checked the base64url encoding. It seems correct. Here’s the snippet for generating the challenge.
async function generatePKCE() {
const verifier = crypto.getRandomValues(new Uint8Array(32));
const encoded = btoa(String.fromCharCode(...verifier)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(encoded));
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
return { verifier: encoded, challenge };
}
I pass code_challenge_method=S256 in the auth request. Then I send the code and code_verifier to /oauth/token. The token endpoint rejects it. I’ve tried raw base64 too. No luck. The docs mention specific encoding rules but they’re vague on edge cases. Is there a library I should use instead of manual encoding? Or am I missing a step in the verifier generation? The error doesn’t give much detail. Just invalid_grant. I need to get this working before the sprint ends. Any ideas why the verifier mismatch happens?