Error Encyclopedia

OAuth Invalid Grant Error

Fix 'invalid_grant' OAuth 2.0 errors when exchanging authorization codes or refreshing tokens.

What Does This Error Mean?

The 'invalid_grant' error is an OAuth 2.0 error indicating that the authorization grant (authorization code, refresh token, or resource owner credentials) is invalid, expired, or was already used.

Common Causes

1

Authorization code was already used (it's a one-time use code)

2

Authorization code has expired (typically valid for 1-10 minutes)

3

Refresh token was revoked or has expired

4

Authorization code was issued for a different redirect_uri than the one used

5

PKCE code_verifier does not match the code_challenge

6

Client credentials (client_id/client_secret) are incorrect

How to Fix It

Check authorization code usage

Authorization codes are single-use. You must get a new one for each token request.

// ❌ Error: reusing the same code
const code = "abc123"
await exchangeCode(code) // Works first time
await exchangeCode(code) // invalid_grant: already used

// ✅ Get a new authorization code each time
// Redirect user to authorization URL
const authUrl = `https://auth.example.com/authorize?
  response_type=code&
  client_id=${clientId}&
  redirect_uri=${redirectUri}&
  state=${state}`
// User authorizes, gets redirected back with a new code

Handle authorization code expiration

Authorization codes are short-lived. Exchange them immediately.

app.get("/callback", async (req, res) => {
  const { code, state } = req.query
  
  // Exchange immediately — codes expire in ~10 minutes
  const tokenResponse = await fetch("https://auth.example.com/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: process.env.REDIRECT_URI,
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      code_verifier: pkceVerifier, // For PKCE flow
    })
  })
})

Implement refresh token rotation

Replace refresh tokens on each use to prevent replay attacks.

// When refreshing, expect a NEW refresh token in response
async function refreshAccessToken(refreshToken) {
  const res = await fetch("https://auth.example.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: process.env.CLIENT_ID,
    })
  })
  
  if (res.ok) {
    const { access_token, refresh_token: newRefreshToken } = await res.json()
    // Store the NEW refresh token
    await storage.set("refreshToken", newRefreshToken)
    return access_token
  }
}

Related Tools

Use these tools to debug and fix this error:

Related Errors

Other common errors in this category: