Why PKCE matters
Without PKCE, a leaked authorization code can be exchanged for tokens by anyone who intercepts it. PKCE binds the code exchange to the specific browser session that initiated the login:- Your app generates a random
code_verifierstring - It hashes it to produce a
code_challengeand sends that to Ave - When exchanging the code for tokens, it sends the original
code_verifier - Ave hashes the verifier and checks it matches the stored challenge
code_verifier, which never left your app.
Implementation checklist
Generate a high-entropy
code_verifier (at least 43 characters, URL-safe)Hash it with SHA-256 and base64url-encode to get
code_challengeSend
code_challenge and code_challenge_method=S256 in the authorization requestStore
code_verifier, state, and nonce in sessionStorage (not localStorage)Verify the returned
state on the callback before exchanging the code — prevents CSRF attacksClear stored values immediately after a successful exchange — they are single-use
Implementation with @ave-id/sdk
Common PKCE failures
invalid_grant: code_verifier mismatch
invalid_grant: code_verifier mismatch
The
code_verifier stored before the redirect doesn’t match what you send at exchange time.Most common causes:- User opened a second browser tab, overwriting the stored verifier
sessionStoragewas cleared between redirect and callback (e.g., redirect to a new window)- Base64url encoding bug — the verifier must use URL-safe characters (
-and_, not+and/)
btoa() output is made URL-safe.code_verifier missing from token request
code_verifier missing from token request
Token exchange returns
invalid_request even though you’re sending the code.Cause: Forgetting to include codeVerifier in the token exchange request when the authorization request included a code_challenge.Fix: Always include codeVerifier when using PKCE — even when using a confidential client that also sends a clientSecret.Callback route handling twice
Callback route handling twice
Token exchange fires twice, and the second call fails with
invalid_grant (code already used).Cause: React strict mode, HMR, or a route that mounts twice.Fix: Use a ref or state flag to ensure exchangeCode is called exactly once per page load. In React:Mobile-specific concerns
Deep links and custom URI schemes
Deep links and custom URI schemes
If you use a deep link (
myapp://callback) as your redirect_uri, validate state before trusting any parameters — deep links can be intercepted by other apps on the device.App lifecycle and storage
App lifecycle and storage
On iOS and Android, the app may be backgrounded or terminated between the authorization redirect and the callback. Use app-lifecycle-safe storage (e.g. encrypted shared preferences on Android, Keychain on iOS) rather than in-memory session storage.
Duplicate callback intents
Duplicate callback intents
Some Android launchers can deliver the same deep link intent multiple times. Guard against this with the same deduplication pattern as the React strict mode fix above.
