Join our Newsletter — 33% off our NHI Course
Home› Guides› OAuth invalid_grant Error Troubleshooting Guide: Finding Why Codes,…
Guide Authorisation

OAuth invalid_grant Error Troubleshooting Guide: Finding Why Codes, Refresh Tokens and Assertions Fail

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 14 min read
On this page

The token endpoint returns invalid_grant for at least a dozen different reasons, from a reused authorisation code to a refresh token revoked by a password reset, and the bare error code never says which. RFC 6749 uses one code for any grant or refresh token that is invalid, expired, revoked, issued to another client or bound to a different redirect URI, and later specifications (PKCE, JWT bearer grants, refresh token rotation) added more causes under the same name. This guide works from the symptom to the cause: which grant failed, what the provider-specific detail says, where to look, the fix, and how to stop it recurring without weakening token security. It is for developers, identity engineers and platform teams running OAuth clients, including integrations and service accounts. Provider error texts were checked against Microsoft, Google and Okta documentation on 11 November 2026. Client authentication errors (invalid_client, unauthorized_client) have their own OAuth invalid_client and unauthorized_client Troubleshooting Guide.

Key takeaways

  • Identify the grant type first. invalid_grant on an authorisation code exchange, a refresh, and a JWT bearer assertion have almost no causes in common. The grant_type in the failing request halves the search immediately.
  • The provider detail is the real error. Read error_description, Entra's AADSTS number, Google's error_subtype or the Okta System Log event. They distinguish expiry, revocation, reuse and mismatch where the OAuth code cannot.
  • Many refresh failures are expected lifecycle events. Inactivity limits, password resets, admin revocation and Google's 100-token limit per account and client all end a grant by design. The fix is graceful re-authorisation, not a longer token.
  • Concurrency causes a large share of rotation failures. Two workers refreshing the same rotated token at once looks exactly like token theft to the authorisation server, and can revoke the whole grant.
  • Never "fix" invalid_grant by weakening controls. Disabling PKCE, rotation or reuse detection, or switching to a long-lived static key, turns an outage into an exposure.

What invalid_grant means, and what it does not

RFC 6749 section 5.2 returns invalid_grant (HTTP 400) when the authorisation grant or refresh token is invalid, expired or revoked, does not match the redirection URI used in the authorisation request, or was issued to another client. Later standards reuse it: RFC 7636 returns it when the PKCE code_verifier does not match the code_challenge, and RFC 7523 returns it for an invalid JWT used as an authorisation grant. Microsoft's own description is broader still: some of the authentication material (auth code, refresh token, access token or PKCE challenge) was invalid, unparseable, missing or otherwise unusable.

It is not a client authentication error. If the client's secret, certificate or assertion is wrong, the server returns invalid_client; if the client may not use the grant type, unauthorized_client. A wrong scope is invalid_scope. If you see one of those, you are on the wrong page.

Triage: narrow it down in five questions

  1. Which grant? Check grant_type: authorization_code, refresh_token, urn:ietf:params:oauth:grant-type:jwt-bearer, urn:ietf:params:oauth:grant-type:token-exchange or a device code.
  2. What is the provider detail? Log the full JSON error body (never the token itself). Entra puts the AADSTS code and a trace ID in error_description; Google adds error_subtype in some cases.
  3. Did it ever work? A first-time failure points at configuration (redirect URI, PKCE, assertion claims). A failure after weeks of success points at lifecycle (expiry, revocation, rotation).
  4. How many users or instances? One user suggests that user's grant was revoked or expired. Every user at once suggests a deployment change, a key rotation or a server-side policy change.
  5. Is there concurrency? Multiple pods, threads, browser tabs or serverless invocations sharing one refresh token is the first suspect for intermittent refresh failures.

Authorisation code exchange failures

CauseEvidenceFixPrevent
Code already redeemedEntra AADSTS54005 "OAuth2 Authorization code was already redeemed"; duplicate POSTs in app logsStart a new authorisation requestExchange the code once, server-side; guard against double form submits, retries on timeout and browser back. RFC 9700 requires codes to be invalidated after first use
Code expiredDelay between redirect and exchange; Entra AADSTS50089 "flow token expired"New authorisation requestExchange immediately. RFC 6749 recommends a maximum code lifetime of 10 minutes; many servers use far less
redirect_uri mismatch at the token endpointValue sent to /token differs from the authorisation request (scheme, port, trailing slash, encoding)Send the identical valueOne configuration constant for both calls. RFC 6749 4.1.3 requires the values to be identical
PKCE verifier mismatchEntra AADSTS501481 or AADSTS50148; verifier from a different session, a stale cookie or a load-balanced instance without shared session stateRestart the flowStore the verifier with the state value in the user's session; use S256; verifier 43 to 128 unreserved characters (RFC 7636)
Code issued to another clientMultiple app registrations or environments, code from staging exchanged with production credentialsUse the client that started the flowSeparate callback URLs per environment
Code corrupted in transitCode contains characters altered by double URL-decoding or truncated by a proxy log limitDecode oncePass the code through unchanged; test with codes containing reserved characters

Refresh token failures

A refresh that worked yesterday and fails today is usually a lifecycle event. Confirm which one before changing code.

CauseProvider evidence (documented)Expected?Action
Inactivity expiryEntra AADSTS700082 "The refresh token has expired due to inactivity" and AADSTS70008; Google: unused for six months; Okta: expires after seven days of non-use with the default unlimited lifetimeYesPrompt for sign-in; for integrations, alert the owner before the idle window ends
Fixed lifetime reachedEntra AADSTS700084 for single-page apps (24-hour lifetime that cannot be extended); 90 days for other Entra scenariosYesRe-run the authorisation code flow
Password changed or resetEntra AADSTS50173 "The provided grant has expired due to it being revoked"; Google: password change when the token has Gmail scopesYesRe-authorise; see revocation rules below
Revoked by user or administratorUser removed app access; admin "revoke sessions"; Google admin_policy_enforced when scopes are restrictedYesRe-authorise, and confirm the revocation was not a security response before re-granting
Session or sign-in frequency policyEntra AADSTS70043 (Conditional Access sign-in frequency); Google Cloud session control returns invalid_grant with error_subtype invalid_rapt, sessions of 1 to 24 hoursYesHandle re-authentication gracefully; do not exempt the app from the policy
Token limit per account and clientGoogle: limit of 100 refresh tokens per Google Account per OAuth client ID; the oldest is invalidated silentlyDesign faultStore and reuse one refresh token per user; do not request prompt=consent on every sign-in
Testing publishing statusGoogle: external apps in Testing status get refresh tokens that expire in 7 daysDesign faultPublish the app for production use
Rotated token reusedOkta System Log app.oauth2.as.token.detect_reuse or app.oauth2.token.detect_reuse; RFC 9700 says the server will revoke the active refresh tokenPossible attack or raceInvestigate first (next section)
Token binding failureEntra AADSTS70000 with token binding header empty or hash mismatchConfigurationCheck the client's proof-of-possession implementation

Revocation rules worth knowing

  • Microsoft Entra: refresh tokens are bound to a user and client, not to a resource. A user changing their password revokes tokens obtained with password-based sign-in but not those from passwordless sign-in. An admin password reset in the Entra admin center also revokes confidential client tokens. Revoking all refresh tokens for a user revokes everything. B2B guest tokens are revoked in the home tenant, not the resource tenant.
  • Entra token reuse: Microsoft says the platform does not revoke old refresh tokens when they are used, but apps should delete the old token after receiving a new one.
  • Google: the six-month idle rule, password change with Gmail scopes, user revocation, time-based access ending and the 100-token limit all end the grant.

Rotation, concurrency and reuse detection

RFC 9700 (BCP 240, January 2025) requires refresh tokens for public clients to be sender-constrained or rotated. With rotation, each refresh issues a new refresh token and invalidates the old one; if an invalidated token is presented again, the server cannot tell the attacker from the legitimate client, so it revokes the active token and the client must obtain a new grant.

  • Symptom of a race: intermittent invalid_grant on refresh after deploying more replicas, adding background sync, or opening the app in several tabs; failures cluster at access token expiry when every worker refreshes at once.
  • Diagnosis: correlate failure timestamps with refresh calls from different instances for the same user or integration. Two refreshes with the same token within seconds confirm it. Okta logs the reuse event.
  • Fix: make refresh a single-flight operation per grant: one lock (distributed if needed), refresh, persist the new token atomically, then release. Other workers wait and read the new token.
  • Grace periods: Okta allows a configurable grace period of 0 to 60 seconds (default 30) in which the previous token remains valid. It masks small races; it does not fix a design where many workers hold their own copy.
  • When it is not a race: reuse from a different IP range, user agent or ASN than the legitimate client is a theft indicator. Treat it as an incident and see How to detect use of a stolen OAuth refresh token.

Rotation and expiry design choices are covered in the OAuth Refresh Token Rotation and Expiry Guide.

JWT bearer and assertion grants

Service accounts and server integrations often use a signed JWT as the grant (RFC 7523). Any validation failure returns invalid_grant: the aud must identify the authorisation server, the current time must be inside nbf and exp, and the signature must verify. Google documents these exact messages for its service account flow:

Google error textCauseFix
"Invalid JWT Signature."Signing key deleted, disabled or expired, or not tied to the service account; assertion encoded with newlines or paddingCheck the key ID against the service account's active keys; Base64url-encode without padding
"Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe."Signing host clock is wrong, or exp more than an hour after iatSynchronise time (NTP); set exp no more than one hour after iat
"Not a valid email" or "Invalid email or User ID."The user in sub (domain-wide delegation) does not existCorrect the impersonated user

For federated workload tokens exchanged at a cloud security token service, see the Multi-Cloud Workload Identity Troubleshooting Guide; for claim and signature checks in general, the JWT Validation Errors Troubleshooting Guide.

Choosing the right fix

SituationRight responseWrong response
Interactive user app, grant expired or revokedCatch invalid_grant, clear stored tokens, send the user through sign-inRetrying the refresh in a loop (Entra AADSTS50196 detects client loops)
Unattended integration acting for a userAlert the named owner to re-authorise; track idle and lifetime limitsMoving to a shared admin account with a non-expiring token
Server-to-server with no userUse the client credentials grant or workload federation, which have no refresh token to loseKeeping a refresh token from a person who has left
Rotation races in a scaled serviceSingle-flight refresh with shared storageDisabling rotation or reuse detection
Small organisation, few integrationsManual re-authorisation with an owner and calendar reminder can be enoughIgnoring failures until a business process breaks
Large estateCentral token broker or secrets platform, with failure telemetry per integrationEach team writing its own refresh logic

For unattended workloads, see the Machine-to-Machine OAuth (Client Credentials) Guide.

Diagnosis workflow, testing and rollback

  1. Capture the failing request safely: method, endpoint, grant_type, client ID, redirect URI and the full error body. Redact codes, tokens, verifiers and secrets.
  2. Pull the server-side record: Entra sign-in logs by trace or correlation ID, Okta System Log, Google Cloud audit logs or the provider's equivalent. The server log usually states the reason the response hides.
  3. Classify: configuration (first-time, every user), lifecycle (after a period, one user), or concurrency (intermittent, scaled).
  4. Reproduce in a test tenant with the same client settings, then change one variable at a time.
  5. Deploy the fix behind a flag or to one instance first; keep the previous release ready to restore. Never roll back by disabling PKCE or rotation in production.
  6. Confirm the failure rate drops and no new reuse events appear for seven days.

Where identity fits

A refresh token held by an integration is a long-lived non-human credential acting with a human's authority. When the person leaves, changes password or revokes consent, the integration breaks, which is the correct outcome. Each integration needs a named owner, an inventory entry and monitoring of its refresh failures. Revocation is also a containment step: after the Salesloft Drift breach, where stolen OAuth tokens from one integration opened hundreds of Salesforce tenants, revoking the integration's tokens is exactly the action that produces invalid_grant for the attacker and the legitimate connection alike. Re-grant only after the cause is understood, using the OAuth Token Theft Incident Playbook.

Detection and measurement

  • invalid_grant rate per client and grant type: a step change after a release points at configuration; a slow rise points at lifecycle.
  • Refresh token reuse events: any Okta detect_reuse event or equivalent, investigated within a set time.
  • Integrations failing silently: unattended clients with repeated refresh failures and no successful call in 24 hours.
  • Re-authorisation lead time: integrations within 14 days of an idle or lifetime limit.
  • Time to restore: from first failure to working grant for business-critical integrations.

Common failure patterns

  • Retry storms: treating invalid_grant as transient and retrying it, which never succeeds and can trip loop detection.
  • Consent on every login: forcing a new refresh token each sign-in until the oldest are silently invalidated by a per-account limit.
  • Shared refresh tokens across replicas: rotation and reuse detection then revoke the grant at random.
  • Integrations owned by leavers: a grant tied to a departed employee's account stops on account disablement, with nobody to re-authorise.
  • Logging tokens to debug: pasting codes and refresh tokens into tickets or logs, creating a credential exposure during the outage.

Framework mapping

FrameworkReferenceRelevance
NIST SP 800-53 Rev. 5IA-5 Authenticator Management; SC-23 Session Authenticity; AC-12 Session Termination; IA-11 Re-authenticationToken lifetimes, rotation and forced re-authentication
NIST CSF 2.0PR.AA-01; PR.AA-03; DE.CM-09Managing and authenticating service credentials, monitoring use
ISO/IEC 27001:2022A.8.5 Secure authenticationSecure token handling and re-authentication
MITRE ATT&CKT1528 Steal Application Access Token; T1550.001 Application Access TokenTheft and replay that reuse detection and revocation counter
IETFRFC 6749 s5.2; RFC 7636 s4.6; RFC 7523 s3.1; RFC 9700 s4.14Where invalid_grant is defined for each grant

Practitioner checklist

  • Log the full error body, AADSTS code, error_subtype and trace ID for every token endpoint failure, never the token.
  • Classify each failure by grant type before investigating.
  • Treat invalid_grant as permanent for that grant: no automatic retries.
  • Store the PKCE verifier with state in shared session storage.
  • Use one constant for redirect_uri in both authorisation and token requests.
  • Implement single-flight refresh and atomic token storage for scaled services.
  • Give every user-delegated integration a named owner and alert before idle limits.
  • Keep signing hosts on synchronised time for JWT assertion grants.
  • Investigate refresh token reuse events as potential theft.
  • Move unattended server workloads to client credentials or workload federation.

Standards and references

Related NHI Mgmt Group resources: OAuth Refresh Token Rotation and Expiry Guide · OAuth invalid_client and unauthorized_client Troubleshooting Guide · OAuth 2.0 and OpenID Connect Guide · How to detect use of a stolen OAuth refresh token

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org