Skip to main content

Token Revocation

Token Revocation (RFC 7009) lets a client tell ThunderID that a token should stop working before it expires. ThunderID extends this with two broader scopes, so an operational event such as a sign-out or a user deletion can invalidate many tokens at once.

Access tokens are self-contained JWTs, so a resource server that validates them locally has no way to know a token was revoked. ThunderID solves this with a denylist that both the authorization server and resource servers consult. The three scopes below differ only in what goes on that list.

The Three Scopes

ScopeWhat it invalidatesTypical trigger
Single tokenOne token, by its jtiA client calls /oauth2/revoke; a refresh token is rotated
GrantEvery token of one authorization grant, by its tfidSign-out, refresh-token replay, authorization-code replay
UserEvery token belonging to one user, by its subA user is deleted

A token is rejected if any of these matches.

Revoke a Single Token

How ThunderID Implements It
AspectBehavior
EndpointPOST /oauth2/revoke
AuthenticationRequired, same client authentication method as the token endpoint (see Client Authentication Methods)
token parameterRequired. The access token or refresh token to revoke
token_type_hint parameterAccepted but not used internally: the server determines the token type itself
Success responseHTTP 200 with an empty body, as RFC 7009 §2.2 requires
Unknown or already-expired tokenHTTP 200. The endpoint never reveals whether a token existed
Token issued to another clientHTTP 400 with invalid_grant
Tokens supportedAccess tokens and refresh tokens
SignatureVerified before anything is recorded, so a token ThunderID did not issue cannot be added to the denylist
Expired tokensStill revocable. Expiry is ignored on this endpoint, so an expired token can be presented to revoke the rest of its grant
curl -X POST https://thunderid.example.com/oauth2/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "token=$ACCESS_TOKEN"

A successful revocation returns 200 OK with no body.

Ownership

RFC 7009 §2.1 requires the server to verify that the token was issued to the authenticated client. ThunderID enforces this for both token types: access tokens are matched on their client_id claim, and refresh tokens on their sub claim, which carries the owning client. A client attempting to revoke another client's token receives invalid_grant.

Grant-Scoped Revocation

Revoking one token of a grant usually should not leave its siblings working. Five refreshes produce a chain of access and refresh tokens that all descend from the same authorization, and revoking the newest refresh token alone would leave live access tokens behind.

ThunderID groups these into a token family, identified by a tfid claim carried on every token of the grant. By default, revoking any token of a family revokes the family in both directions: revoking a refresh token invalidates the access tokens issued alongside it, and revoking an access token invalidates the refresh token that would mint replacements. What Revokes a Family below lists the setting that controls this, along with the other triggers.

Where tfid Comes From

FlowBehavior
Authorization code with SSOMinted when the session is established and stored against the session participant, which is what makes sign-out revocation possible
Authorization code without SSOMinted when the authorization code is created, so the grant is still revocable
RefreshCopied to the new tokens, so a family survives rotation
Token exchangeControlled by oauth.token_exchange.token_family. none (the default) issues an independent token; inherit copies the subject token's family
Client credentials, CIBANo tfid. There is no user grant to group, so family-scoped revocation does not apply

What Revokes a Family

TriggerSettingDefault
A client revokes a token that carries a tfidoauth.revocation.token_family.on_explicit_revoketrue
A rotated-out refresh token is presented again (replay)oauth.revocation.token_family.on_refresh_replaytrue
An authorization code is redeemed twice (replay)oauth.revocation.token_family.on_code_replaytrue
The user signs out through RP-Initiated LogoutAlways onNot configurable
note

Refresh-token replay detection depends on rotation, since a token can only be replayed once it has been rotated out. Enable oauth.refresh_token.renew_on_grant to use it.

Session sign-out revokes the family. Session expiry does not: a session that simply times out leaves its tokens valid until they expire on their own.

User-Scoped Revocation

Some events invalidate tokens that no single client is holding, and that span more than one grant. Deleting a user is the clearest case: their tokens may be spread across several applications and devices, and nobody presents them to be revoked.

For these, ThunderID revokes by subject: every token issued to that user is rejected, across every application and device, without anyone having to present or enumerate them. Tokens already carry sub, so no additional claim is needed.

User Deletion

ThunderID ships an administration flow that runs when a user is deleted from the Console. It checks the caller's permission, revokes the user's tokens by subject, terminates their sessions, and then removes the record. flow.userDeletionFlow.defaultHandle names the flow to run. Pointing it at a different administration flow replaces the shipped one.

warning

Deleting a user through the DELETE /users/{id} API removes the record only. It does not run the deletion flow, so the user's tokens stay valid until they expire. To revoke tokens as part of deletion, delete from the Console, or run the deletion flow directly from automation.

Where Revocation Is Enforced

Revocation is checked at two independent points, and they do not become consistent at the same speed.

Enforcement pointWhat it coversFreshness
Authorization serverIntrospection, the refresh grant, token exchange, and internal validationImmediate. Reads the denylist directly
Resource serverRequests to ThunderID's own protected APIsEventually consistent. Serves an in-memory snapshot refreshed on an interval
note

A revoked token normally stops being accepted by a resource server within server.security.token_revocation.sync_interval_seconds (60 seconds by default). For an immediate answer, call Token Introspection, which reads current state. Lower the sync interval to shorten the delay, at the cost of more frequent database reads.

Authorization-server enforcement fails closed. If the denylist cannot be read, token validation fails rather than assuming the token is good. Resource-server enforcement keeps serving its last good snapshot across a transient refresh failure.

Resource-server enforcement covers services that share this deployment's database. An external resource server with no access to it should use Token Introspection instead.

Configuration

Revocation behavior on the authorization server, part of OAuth Configuration:

SettingDefaultDescription
oauth.token_revocation.enabledtrueEnables revocation on the authorization server: the /oauth2/revoke endpoint, the implicit triggers below, and the check that rejects revoked tokens during introspection, the refresh grant, and token exchange. Setting it to false turns off all of them, not just the endpoint
oauth.revocation.token_family.on_explicit_revoketrueRevoking a token also revokes its token family
oauth.revocation.token_family.on_refresh_replaytrueA replayed refresh token revokes its token family. Requires oauth.refresh_token.renew_on_grant
oauth.revocation.token_family.on_code_replaytrueA replayed authorization code revokes the token family issued from it
oauth.token_exchange.token_familynonenone issues an independent token; inherit keeps the subject token's family
oauth.refresh_token.renew_on_grantfalseIssues a new refresh token on each grant. Required for rotation and replay detection
oauth.refresh_token.revoke_previous_on_renewtrueRevokes the consumed refresh token after rotation, making refresh tokens single-use

Revocation enforcement on resource-server requests, part of Security Configuration:

SettingDefaultDescription
server.security.token_revocation.enabledtrueEnforces revocation on requests to protected APIs
server.security.token_revocation.sourcedbWhere the revocation snapshot is read from. db is the only supported value
server.security.token_revocation.sync_interval_seconds60How often the snapshot refreshes, which sets the normal propagation delay before a revoked token stops being accepted

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.