Replies: 4 comments 4 replies
|
Hi @indeewari |
|
Hi @indeewari, And may also need to check when DPoP bound tokens are needed to be revoked via the revocation endpoint, whether the revocation request needs to be sent with the DPoP proof. |
|
We also have a |
|
@indeewari , I have below question here?
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Related Feature Issue
Problem Summary
Thunder ID was initially designed without a token revocation mechanism — a deliberate choice, as self-contained JWTs with short expiry are the approach recommended by OAuth 2.0 best practices for stateless systems. For many deployments, short-lived access tokens are sufficient.
However, short expiry alone does not cover all revocation requirements. Once a token is issued, there is no mechanism to invalidate it before its
expclaim is reached, even if the user is deleted, loses a role, the client is deregistered, or a security compromise is detected. The design below introduces a server-side blocklist to close this window, in phases that each deliver independently useful revocation capability.High-Level Approach
Because Thunder ID's tokens are stateless JWTs, revocation is implemented as a server-side blocklist: when a token is revoked, a lightweight record is written and every subsequent validation at Thunder ID gates on that list. Resource servers can verify token validity by calling
POST /oauth2/introspect, but making that call on every incoming request adds latency that most resource servers prefer to avoid when validating JWTs locally. Therefore introduces a push notification mechanism: on every write to the blocklist or criteria store, Thunder ID notifies resource servers so they can update a local revocation cache and check it locally — no per-request call needed.At an abstract level, token revocation in an authorization server can be categorized into two flows. The first is single-token revocation, which includes direct revocation via an API call or auth code replay — in these cases, the JTI of the revoked token can be explicitly identified. The second is bulk token revocation, which applies in scenarios such as client secret regeneration or changes to user permissions, where multiple tokens are invalidated at once rather than individually. Here we identify a token revocation criteria rather than a JTI.
flowchart TB A["Token Revocation"] B["Single token revocation"] JTI[("Token deny list by JTI")] C["Bulk token revocation"] Criteria[("Token deny criteria")] A --> B B --> JTI A --> C C --> CriteriaBased on the above, our first milestone is to focus on the single-token revocation path.
flowchart TB subgraph ThunderID["ThunderID"] subgraph ThunderAS["ThunderID Authorization Server"] direction TB SingleRevoke["Single token revocation"] RevocationService["Revocation Service"] RevocationStore[("Enforcement DB\n(Token deny list by JTI)")] Notifier["Notifier"] subgraph TokenValidatorAS["Token Validator"] direction LR ATValidator["Access token validator"] RTValidator["Refresh token validator"] ACValidator["Auth code validator"] end subgraph InternalAS["OAuth Services"] direction TB TokenExchange["Token exchange"] Introspection["Introspect"] RevokeToken["Revoke token"] RefreshToken["Refresh token"] IssueToken["Issue token\n(Auth Code replay)"] end RevokeToken -->|"Revoke token"| SingleRevoke RefreshToken -->|"Revoke token"| SingleRevoke RefreshToken --> TokenValidatorAS IssueToken -->|"Revoke token if replay detected"| SingleRevoke SingleRevoke --> RevocationService RevocationService -->|"Add to the deny list"| RevocationStore RevocationService -->|"Publish to internal notification layer"| Notifier TokenExchange -->|"Validate token before exchange"| TokenValidatorAS Introspection -->|"Validate token to get token status"| TokenValidatorAS TokenValidatorAS -->|"Check deny list hit"| RevocationStore end subgraph ThunderRS["ThunderID Resource Server"] direction TB NotificationHandler["Notification Handler"] RevocationCacheService["Revocation Cache Service"] RevocationCache[("Revocation Cache")] TokenValidatorRS["Token Validator"] subgraph InternalRS["Resource Access Authorization"] direction TB AdminAPIs["Admin APIs"] MCPServerAccess["MCP Server Access"] UserInfo["User Info"] end AdminAPIs --> TokenValidatorRS MCPServerAccess --> TokenValidatorRS UserInfo --> TokenValidatorRS TokenValidatorRS --> RevocationCacheService NotificationHandler --> RevocationCacheService RevocationCacheService --> RevocationCache end end subgraph ExternalRS["External Resource Server"] direction TB Webhook["Notification Endpoint"] Cache[("Local Cache")] TokenCheck["Token Validator"] Authz["Resource Access Authorization"] Webhook --> Cache Authz --> TokenCheck TokenCheck --> Cache end subgraph Legend["Legend"] direction LR CoreLegend["Core architectural modification"] FeatureLegend["Feature-level modification"] end ExternalWebhookManager["External Webhook Manager"] Notifier --> ExternalWebhookManager ExternalWebhookManager --> NotificationHandler ExternalWebhookManager --> Webhook classDef dotted stroke-dasharray: 4 4,fill:none classDef coreStyle fill:#FFE8CC,stroke:#FF9900,color:#000000 classDef featureStyle fill:#DBEAFE,stroke:#3B82F6,color:#000000 class ThunderID dotted class RevocationStore,NotificationHandler coreStyle class RevokeToken,SingleRevoke,RevocationService,RevocationCache,RevocationCacheService,TokenValidatorAS,Notifier,ExternalWebhookManager,TokenValidatorRS featureStyle class CoreLegend coreStyle class FeatureLegend featureStyleCore Architectural Modifications
Token revocation data does not fit ThunderID's existing database classifications. Runtime DB is for short-lived transactional state; criteria-based revocation records (by sub or client_id) have no natural expiry and must survive a Runtime DB flush. Config DB holds system configuration, not runtime enforcement decisions. User DB holds identity, not authorization state.
Revocation data is event-driven at runtime but must persist beyond any single session — a combination no existing classification addresses. This introduces database.enforcement: the authoritative store for authorization enforcement state, including token revocation records and consent history. It establishes a foundation for future enforcement-oriented data such as audit logs.
ThunderID's own Resource Server have no facility to receive or respond to revocation events. A notification listening mechanism is introduced, allowing revocation events from the Authorization Server to be delivered in-process via the internal event bus, populating a local Revocation Cache that token validators consult at request time — without a network round-trip to the introspection endpoint.
Feature-Level Modifications
ThunderID Authorization Server
Implements RFC 7009, enabling clients and administrators to explicitly revoke a specific token by presentation. The token's JTI is written to the Revocation Store and a TOKEN_REVOKED event is published to trigger downstream notification.
HTTP contract:
New table REVOKED_TOKEN in database.enforcement stores one row per revoked token JTI. Only single-token revocation flows write to this table.
Admin governance for implicit revocation
Introduces server-level configuration to govern implicit revocation behaviour on the single-token path — including refresh token rotation enforcement and revocation of tokens issued from a replayed authorization code.
Token validation against revocation data
Extends the shared token validation path (introspection, refresh grant, token exchange) to consult the persisted revocation data on every validation. Tokens whose JTI or issuing principal appears in the store are rejected regardless of cryptographic validity.
Outbound notification to external Resource Servers
When a token is revoked, the Revocation Notifier resolves target Resource Servers from the token's aud claim and dispatches a signed webhook payload via the External Webhook Manager, with configurable retry and backoff.
ThunderID Resource Server
Revocation Cache
Introduces a local Revocation Cache, populated by inbound revocation notifications.
Token validation against revocation cache
Extends token validation on all first-party Resource Server surfaces to consult the Revocation Cache on each request. A cache hit results in immediate rejection. A cache miss bounds worst-case exposure to the token's natural lifetime.
Architecture Overview
Revocation dimensions
A token is considered revoked if any one of these dimensions returns a hit at the
blocklist / criteria:
jtiPOST /oauth2/revoke; old RT on rotationclient_idDELETE /oauth2/dcr/register/{client_id}subrevoked_at >= token.iatsub:client_idrevoked_at >= token.iatsubrevoked_at >= token.iatend_session_endpoint) — Phase 3Flows covered
Flows that triggers token revocation
POST /oauth2/revokeinvalid_grantreturned; grant-level deferredinvalid_grantreturned; grant-level deferredDELETE /oauth2/dcr/register/{client_id}end_session_endpointoffline_accesstokens exemptFlows that requires to evaluate the validity of the token once token revocation is introduced
/oauth2/introspect)/oauth2/userinfo)/mcp/**)[Future] Introducing a grant store
A new table will be introduced to manage grants, enabling users to list and
revoke their active sessions per device to handle offline_access cases.
Security Considerations
Token ownership enforcement. The revocation service verifies the token was issued to the requesting client. A mismatch returns
invalid_grant, preventing a client from revoking another client's tokens.Expired tokens remain revocable. Signature-only verification is used —blocking on expiry creates a race condition at token expiry and breaks revocation under clock skew.
User deletion leaves no window. Forced subject revocation applies regardless of client or
offline_accessscope. A deleted or suspended user's tokens are invalid on the next validation call.Runtime database erasure is bounded. Loss of the token blocklist is bounded by token lifetime. Criteria records survive because they live in the config database.
Push notification delivery is best-effort with bounded miss window. If a resource server misses a push notification (due to downtime or network partition), it continues to accept revoked tokens until the next successful startup sync. The miss window is bounded by the access token's remaining lifetime — after
exp, the token is rejected by standard expiry validation regardless.Impacted Areas
Alternatives Considered
Single table in the runtime database. A runtime database wipe would silently restore access for deleted users and deregistered clients. Two tables split by durability requirement — runtime for JTI entries, config for criteria — was chosen.
Expiry on criteria records derived from server max token validity. Unsafe when token validity is per-application; criteria records are managed explicitly instead.
Single table per revocation type. A type-discriminator in one criteria table covers all lookup patterns with fewer migrations and cleanup jobs.
User deletion as standard subject revocation. This would leave
offline_accesstokens valid after account deletion. A distinct forced revocation type with no exemption was chosen.Questions for Community Input
All reactions