Getting a 400 Bad Request on the token exchange. The error message says invalid_grant: code_verifier does not match code_challenge. I’ve double-checked the encoding. It’s a base64url string, no padding. Here’s the setup.
Generating the challenge:
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const codeChallenge = hashArray.map(b => String.fromCharCode(b)).join('');
const challenge = btoa(codeChallenge).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
Posting to /api/v2/oauth2/token with code, code_verifier, grant_type: authorization_code, and redirect_uri. The verifier matches the input string exactly. No extra whitespace. The redirect URI in the request matches the one used in the auth code request. I’ve tried sending the verifier as form-urlencoded and as JSON. Same result.
Checked the browser network tab. The verifier looks right. The code is fresh. Maybe the SDK’s token exchange helper is mangling the body? I’m using the raw fetch API for this step to rule out SDK noise. The endpoint is https://api.mypurecloud.com/api/v2/oauth2/token.
Is GC expecting the challenge in a specific format that differs from the RFC? Or is there a hidden requirement for the verifier length? I’ve seen this work in Postman with static values. Fails in the app. Feeling stuck. Here’s the form data body I’m sending.
grant_type=authorization_code&code=AUTH_CODE_HERE&code_verifier=VERIFIER_HERE&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
Headers include Content-Type: application/x-www-form-urlencoded and the Authorization: Basic base64(clientId:clientSecret) header. Still 400. What am I missing?