Token lifecycle
Lifetimes
Section titled “Lifetimes”| Token | Lifetime | Notes |
|---|---|---|
| Authorization code | 60 seconds | Single-use, PKCE-bound |
| Access token | 1 hour | |
| ID token | 1 hour | |
| Delegated token (token exchange) | 5 minutes | Deliberately short — an on-behalf-of token should not outlive the task |
| Refresh token | 30 days | Rotated on every use |
| Verifiable credential | 90 days | |
PAR request_uri | 90 seconds | Single-use |
| Admin Portal link | 15 minutes | Single-use; yields a 1-hour session |
Refresh tokens
Section titled “Refresh tokens”A refresh token is issued only when offline_access was among the granted scopes. It looks like
rt_…, is stored hashed, and rotates on every use: each refresh returns a new token and
consumes the old one.
POST /tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=rt_…&client_id=client_7f3a…{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile offline_access", "refresh_token": "rt_…", "id_token": "eyJ…"}Store the new refresh_token immediately. Continuing to use the previous one is not merely an error
— see below.
You may narrow scope on refresh by passing a scope that is a subset of the original. A request for
anything outside the original grant is ignored rather than honoured, so scope can only ever shrink.
Confidential clients must authenticate to refresh. A DPoP-bound refresh token requires a proof from the same key it was issued under — the binding survives rotation.
Reuse detection
Section titled “Reuse detection”Rotation makes theft detectable. RFC 9700: if a refresh token that has already been rotated is presented again, the legitimate client is holding the successor, so the replay means the token leaked.
The response is 400 invalid_grant — and, more importantly, the entire rotation family is
revoked. Every descendant of that token stops working, and a refresh_reuse_detected event lands
on the audit log.
The same applies to a race: two concurrent requests presenting the same refresh token means only one can win, and the loser is treated as reuse. If your client refreshes from multiple threads or tabs, serialize it behind a single-flight lock. A refresh storm looks exactly like theft, and is handled as such.
If users are being signed out unexpectedly, check for refresh_reuse_detected before assuming a
TTL problem.
Introspection
Section titled “Introspection”RFC 7662. Unlike the rest of the protocol surface, introspection authenticates with a management API key, not a client secret:
POST /introspectAuthorization: Bearer sk_live_…Content-Type: application/x-www-form-urlencoded
token=eyJ…{ "active": true, "token_type": "access_token", "scope": "openid profile", "client_id": "client_7f3a…", "sub": "usr_01J…", "org_id": "org_01J…", "aud": "https://tools.acme.com/mcp"}It accepts both access tokens and refresh tokens, reporting which it found in token_type; a
refresh token also returns exp. Anything unknown, expired, or revoked returns { "active": false }
without further detail.
An org-scoped key introspecting a token from another org gets { "active": false } — not an error,
and not a leak of the token’s contents.
Revocation
Section titled “Revocation”RFC 7009. The client that owns the token revokes it:
POST /revokeAuthorization: Basic <base64(client_id:client_secret)>Content-Type: application/x-www-form-urlencoded
token=rt_…The endpoint always returns 200, even for an unknown token, a token belonging to another client,
or an unparseable one. That is intentional: a revocation endpoint that distinguished these cases
would be an oracle for probing token validity.
- Refresh tokens are revoked directly.
- Access tokens have their
jtidenylisted until their natural expiry, so a revoked access token fails verification for the remainder of its hour rather than staying valid because it is signed.
Revoking one token does not revoke the whole family — for that, either trigger reuse detection or revoke the user’s sessions. To sign a user out everywhere, use session revocation.
Token type
Section titled “Token type”token_type is Bearer unless the token request carried a DPoP proof, in which case it is DPoP
and every subsequent request must present a matching proof. See DPoP.