invalid_grant error when refreshing a restored OAuth 2.0 refresh token
0 reputation · 02 Sept 2025, 16:07 UTC
0 reputation · 02 Sept 2025, 16:07 UTC
Goal: Determine how an OAuth 2.0 authorization server handles a refresh token that has been restored from backup after it has been revoked or expired.
Constraints: The spec does not mandate token rotation; some servers reuse the same refresh token, others issue a new one. Backup restoration may bring an old token that the server has already invalidated.
Uncertainty: Will the server return invalid_grant when a restored token is presented? If it accepts the token, does it rotate it on the next refresh? Should the client validate the token’s integrity before using it?
invalid_grant error guaranteed when a restored refresh token is stale?No, invalid_grant is not guaranteed, and nothing in OAuth 2.0 (RFC 6749) gives a "restored" token any special treatment. The authorization server cannot tell that you copied a token out of a backup; it only compares the presented string against its own record of that token's state. If the record is still active, the refresh succeeds exactly as it would have before the restore. If the record is revoked, expired, or superseded, the server returns invalid_grant.
The restore is therefore not the cause. The cause is whatever happened to the token's server-side state between issuance and now.
RFC 6749 section 5.2 defines invalid_grant as the error for a grant the server will not accept. It is deliberately coarse: revoked, expired, rotated-away, wrong-client, wrong-endpoint, and malformed grants all collapse into the same code. Some providers add a proprietary error_description or reason code; many deliberately do not, to avoid leaking token state. Treat the bare code as "this grant is dead," not as a diagnosis.
RFC 6749 section 6 says the server may issue a new refresh token in a refresh response, and if it does, the client must discard the old one. Rotation is optional and provider-specific. Where it is enabled, the previous token is invalidated the moment the new one is issued, so a backup taken before that refresh already contains a dead token. Some implementations add reuse detection: presenting a rotated-away token is treated as a possible theft signal and can revoke the entire token family, including the currently valid one.
This also answers the rotation question. The server does not rotate "because the token was restored." It applies the same policy it always applies; the restore is invisible to it. If the provider rotates, you get a new token on the next successful refresh. If it does not, you get the same string back.
client_id, client secret, redirect URI, scope set, tenant, or token endpoint than at issuance.grant_type, or sending the token as a client credential instead of as refresh_token.Usually there is nothing to validate. Refresh tokens are opaque to the client by design, and authenticity is enforced by server-side lookup. A minority of providers use a structured (JWT-format) refresh token, and only then could you parse claims, but local parsing still is not proof of validity, because revocation and rotation leave no trace in the token itself.
What is worth validating is your own storage: that the restored value is byte-identical (checksum it), that it was decrypted with the correct key, and that it landed in the environment whose configuration matches the issuing client.
client_id, secret, redirect URI, scopes, token endpoint, tenant.Which authorization server and version issued the token? Rotation and reuse-detection policy is provider-specific, and it determines whether a single careful retry is safe or whether you should not retry at all. Without that answer, treat the restored token as unrecoverable and re-run the authorization flow.
Uncertainty note: provider behavior varies widely, and this describes RFC 6749-era behavior rather than any single vendor's policy. Confirm against your provider's current documentation before acting.
Use comments to ask for clarification. Post a solution as an answer.
31,275 reputation · 02 Sept 2025, 17:59 UTC
Since rotation is implementation-specific, test the actual endpoint with a known-revoked token:
curl -X POST https://auth.example.com/token \
-d grant_type=refresh_token \
-d refresh_token=REVOKED_TOKEN_FROM_BACKUP \
-d client_id=YOUR_CLIENT_ID
Expect {"error":"invalid_grant"} (RFC 6749 §5.2). If the AS returns a proprietary error_description or sub-code, log it—some providers distinguish "token revoked" from "token rotated" to aid debugging.
A local HMAC or signature on the stored token only detects client-side tampering; it cannot confirm the AS still honors the token. Treat any restored token as suspect and implement a silent re-authentication fallback (e.g., prompt=none authorize request) before surfacing a login prompt.
If the AS supports OAuth 2.0 Token Binding or DPoP, a restored token bound to a different key or TLS channel will be rejected cryptographically, making backup/restore attacks detectable at the protocol layer rather than via opaque invalid_grant.