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
Authorization code was already used (it's a one-time use code)
Authorization code has expired (typically valid for 1-10 minutes)
Refresh token was revoked or has expired
Authorization code was issued for a different redirect_uri than the one used
PKCE code_verifier does not match the code_challenge
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 codeHandle 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:
401 Unauthorized Error
Learn what a 401 Unauthorized error means, common causes, and how to fix authentication failures in your web applications.
403 Forbidden Error
Learn what 403 Forbidden means, how it differs from 401, and how to fix access denied errors in your applications.
404 Not Found Error
Learn what 404 Not Found means, common causes, and how to fix broken links and missing resources on your website or API.
429 Too Many Requests Error
Learn what 429 Too Many Requests means, how rate limiting works, and how to handle or avoid hitting API rate limits.