invalid_grant: <reason>
Salesforce rejected the OAuth credentials in your token request. The `error_description` field tells you which part — refresh token revoked/expired, client credentials wrong, redirect URI mismatch, MFA required. Each has a different fix.
Also seen asinvalid_grant·invalid_grant: invalid·invalid_grant: expired·OAuth invalid_grant
invalid_grant is the OAuth 2.0 standard error for "your authorization request was malformed or denied." Salesforce uses it for several specific cases. The error_description in the response narrows it down.
Common error_description payloads
{
"error": "invalid_grant",
"error_description": "expired access/refresh token"
}
{
"error": "invalid_grant",
"error_description": "authentication failure - invalid client credentials"
}
{
"error": "invalid_grant",
"error_description": "redirect uri does not match configuration"
}
{
"error": "invalid_grant",
"error_description": "user hasn't approved this consumer"
}
{
"error": "invalid_grant",
"error_description": "user is locked"
}
Each is a distinct issue.
Fix by description
"expired access/refresh token"
Refresh tokens last a long time (set in the Connected App's Refresh Token Policy: 1 hour, 24 hours, 30 days, 90 days, immediately expire after use, etc.). They can also be revoked manually by the admin.
Fix: trigger the user (or service account) through the OAuth flow again to get a new refresh token. Update your stored token.
"invalid client credentials"
Your client_id (consumer key) or client_secret is wrong. Common after rotating the consumer secret in the Connected App.
Fix: copy the new credentials from Setup → Connected Apps → your app → Manage Consumer Details into your integration's config.
"redirect uri does not match configuration"
OAuth requires the redirect_uri parameter to exactly match a callback URL configured in the Connected App. A trailing slash, http vs https, or different domain — all fail.
Fix: add the exact URI to the Connected App's Callback URLs list. The list supports multiple URLs (one per line).
"user hasn't approved this consumer"
The user hasn't granted permission to the Connected App. Either:
- The user must complete the OAuth consent flow at least once
- The Connected App's policy is Admin Approved Users Only, and the user isn't pre-authorised
Fix: in the Connected App settings, add the user to Permission Set Assignments for that connected app. Or change the policy to All users may self-authorize (less restrictive).
"user is locked"
The user account is deactivated, password-locked, or session-locked. Same triage as INVALID_LOGIN.
A subtle source: clock drift
OAuth tokens carry expiration timestamps. If your server's clock is wrong by more than a few minutes, the platform treats valid tokens as expired. Sync your server's clock to NTP before debugging further.
When the JWT-bearer flow fails with invalid_grant
JWT flow has its own quirks:
- Expired JWT — the JWT's
expclaim is in the past. Generate a new one (typically 3 minutes from now). - Invalid signature — your JWT was signed with the wrong private key, or you're using RS256 when the connected app expects RS512.
- User not pre-authorized — the username in the JWT isn't in the Connected App's permission-set-allowed users.
Each is a different fix. The error_description differentiates them; pay attention.
