OAuth invalid_grant — Fix
invalid_grant has four causes. Each one feels the same from the outside. Here is how to tell them apart and fix each one.
Cause 1 — Authorization code expired (most common)
Authorization codes expire in 10 minutes or less. Exchange the code immediately after the redirect — do not wait.
Cause 2 — Code reused
Each code can only be used once. React re-renders, duplicate API calls, or retries can call the exchange endpoint twice. The second call gets invalid_grant.
// Prevent double exchange
const exchanging = sessionStorage.getItem('exchanging');
if (exchanging) return;
sessionStorage.setItem('exchanging', 'true');
await exchangeCode(code);
sessionStorage.removeItem('exchanging');
Cause 3 — PKCE verifier mismatch
The code_verifier must match the code_challenge sent in the authorization request. If you regenerate it on the callback page, it will not match. Store it before redirecting, retrieve on callback.
Cause 4 — Refresh token revoked or rotated
If you are getting invalid_grant on a refresh call, the refresh token was revoked (user revoked access, password changed) or you used a stale token after rotation. Clear tokens and re-authenticate.
try {
const tokens = await refreshToken(storedRefreshToken);
} catch (err) {
if (err.error === 'invalid_grant') {
clearStoredTokens();
redirectToLogin();
}
}
Debug your invalid_grant error → OAuthFixer