Error

OAuth invalid_grant — Fix

OAuth Error Response
{"error": "invalid_grant", "error_description": "Invalid authorization code"}

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