ProudAuthDocs

Revocation exposure statement#

Requirement SES-023: local JWT verification cannot observe a revocation that happens after the token was issued. This page states, for each kind of consumer and each revocation event, when access actually stops. It applies to the code in this repository with default configuration.

Consumers#

ConsumerHow it validatesSees revocation
IdP's own APIs (/api/v1/realms/{realm}/me/..., tenant and admin APIs, UserInfo)JWT signature, then live checks on every request: JTI revocation list, client active, session active and owned by the subject (user tokens), user able to authenticate, service principal active (machine tokens)Immediately after the revoking transaction commits
Strict-online resource servers (introspection via POST /realms/{realm}/oauth/introspect, e.g. Go SDK verifier.Introspector / SupportGuard)Every request introspected; no positive cacheImmediately for the checks listed under "Introspection checks" below
Offline resource servers (local JWT verification, e.g. Go SDK verifier.Verifier)Signature, issuer, audience, type, times; no server stateNot until the token expires, plus clock tolerance
Bounded-state resource servers (Go SDK verifier.BoundedVerifier with BoundedState, Node.js bounded-state)Local JWT verification plus session, subject, organization and membership revocations replayed from GET /session-eventsAfter the next stream sync that includes the change; checks fail closed (ErrStateStale) once the replayed state is older than MaxStaleness. Revocations before the state's Start are not replayed
Refresh (token endpoint)Live checks in the rotation transactionImmediately: no new access token after revocation
Relying parties with back-channel logoutSigned logout+jwt POSTed by the workerAsynchronously, after the worker delivers (retries up to 8 times)
Webhook subscriberssession.revoked, user.suspended, organization.membership.removed, ...Asynchronously, at least once; not a revocation mechanism (§22)
Shared-signals receivers (AUD-016)CAEP session-revoked and RISC events as Security Event Tokens, pushed by the worker or polled (shared-signals.md)Asynchronously: after the next ssf.deliver run (every 2 s, retries up to 8 times) or the receiver's next poll; only for users the stream covers
Relying parties using the check-session iframe (OIDC-014)Browser cookie compared in an iframeOnly when the browser next reaches ProudAuth (sign-in, silent authorization, logout); revocations made on the server are not visible to the iframe (oauth-oidc-profile.md)

Offline exposure window#

window = remaining token lifetime + verifier clock tolerance

  • Access-token lifetime is set per API resource: access_token_ttl_seconds, default 300 s, range 30–3600 s, optionally shortened per client and capped by tokens.max_access_token_ttl (60 min).
  • Clock tolerance: the Go SDK verifier defaults to 30 s (maximum 60 s); the server's own verifier uses tokens.clock_skew (60 s).
  • Default worst case for an offline verifier: about 5.5 minutes after revocation. Longer resource lifetimes lengthen it proportionally.

Introspection checks#

For JWT access tokens: signature and key not compromised; resource registered for the caller and active; JTI not revoked; client active; user able to authenticate (or service principal active); session active if the token has sid, otherwise (offline grants) no account-wide revocation of the user's tokens at or after the token's iat (SES-005: one lookup on the token_revocations index); membership and organization active if the token has org_id. User and exchanged JWTs also require a signed grant_id whose grant is active, unexpired, in the same realm and owned by the subject. Ordinary user tokens must match its client; an exchange retains its originating grant. UserInfo and IdP user APIs enforce the same live grant rule. A missing grant link fails closed; obtain fresh tokens when upgrading an earlier development build (upgrades.md).

For opaque tokens additionally: token not revoked or expired; grant active. For support tokens: grant active and unexpired; actor and target users active; target membership and organization active; actor session active; actor still holds support.impersonation.start in the organization. For API keys and personal tokens: credential active and unexpired; owner active; owner's membership active when the credential is organization-bound.

Events#

EventIdP APIs and introspectionRefreshOffline JWT verifiersNotifications
Single logout (/oidc/logout, DELETE /me/sessions/{id})Session revoked → tokens with that sid inactive. Grants of that session (except offline grants) and their refresh families revokedDenied for the session's grants; offline grants surviveValid until expiryBack-channel logout for clients with a client session; session.revoked
Logout everywhere (/me/sessions/revoke-all), admin revoke-sessions, user suspend/lock, password marked compromised, password reset, assisted recovery, deletion request, account deletionAll sessions, all grants including offline, refresh families, opaque tokens and support grants (as actor or target) revoked. Every access token of the user issued at or before the revocation is inactive at once, offline-grant tokens without sid included (a user-level token_revocations record, SES-005; a JWT issued in the same second as the revocation counts as before it). Suspension/lock/deletion also make every token of the user inactive (user state check)DeniedValid until expiryBack-channel logout per session; session.revoked, user.suspended, ...
Membership removed or suspendedUser's grants for that organization revoked; tokens with that org_id inactiveDeniedValid until expiryorganization.membership.removed / .suspended
Organization suspended, archived or deletedAll grants and support grants bound to it revoked; tokens with that org_id inactiveDeniedValid until expiryorganization.*
Role or permission changeNot reflected in tokens (tokens carry scopes, not roles). Introspection returns the current authz_version; the decision API evaluates current permissionsScopes unchanged (they follow the client's resource grant and consent)Unchangedauthorization.role.*
Client suspended or decommissionedClient's grants, refresh families and opaque tokens revoked; its tokens inactive (client check)DeniedValid until expiryclient.state_changed
Client secret revokedNo new client authentication with it; issued tokens unaffectedDenied if the client cannot authenticateValid until expiry—
Token revocation endpoint (RFC 7009)Refresh token: its grant, families and opaque tokens revoked; linked JWTs and exchanged descendants inactive online, including offline-grant JWTs without sid. JWT access token: JTI recorded, inactive at introspection and IdP APIs. Opaque token: revokedAs leftJWT still valid until expiry—
Confirmed refresh reuseFamily compromised and grant revoked; linked JWTs, opaque tokens and exchanged descendants inactive online. The session is also revoked for session-bound grants; offline-grant reuse leaves the SSO session intactDenied; authenticate again for that grantValid until expirysecurity.refresh_reuse_detected; session.revoked for a session-bound grant
Consent revoked (DELETE /me/consents/{id}, DELETE /users/{user}/consents/{id}, or a revoked consent pre-authorization)The client's grants for the user, their refresh families and opaque tokens revokedDeniedValid until expiry—
Client consent_terms_version changedNothing revoked; the next authorization asks for consent againUnaffectedUnaffected—
Service principal disabled (also by the owner-departure policy machine.owner_departure: disable)Service tokens inactive; client credentials fail—Valid until expiryservice_principal.state_changed
API key / personal token revoked or owner deactivatedInactive at the next introspection—— (opaque only)—
Support grant revoked or expiredInactive at the next introspectionNever refreshable— (opaque only)support.impersonation.ended (on revoke)
Emergency signing-key rotationTokens signed with the compromised key rejected within 5 sUnaffected (refresh tokens are not signed)Accepted until the verifier drops the key from its JWKS cachesigning_key.rotated
Realm suspendedNew authorizations, token requests and refresh fail; IdP custom APIs rejectDeniedValid until expiry—

Membership states (TEN-003)#

StateHow it arisesSign-in to the organizationToken issuance in its contextLeaves the state when
invitedAn administrator invited an existing account by user_idNoNoAccepted (→ active), or the invitation is revoked, declined, reassigned or expires (→ removed)
pending_approvalThe user filed a join requestNoNoA reviewer approves (→ active) or denies, the user withdraws the request, or it expires after 14 days (→ removed)
activeAcceptance, approval, administrator add, JIT federation, import, creatorYesYes, within roles, scopes and policySuspended, expired or removed
suspendedAn organization administrator suspended the memberNoNo; existing grants, refresh families and opaque tokens in the organization are revoked at onceReactivated (→ active) or removed
expiredThe membership reached its expires_at (worker sweep)NoNo; grants revoked by the sweepNever (a new membership is needed)
removedRemoval, offboarding, deletionNoNo; grants revoked in the removal transaction; organization.membership.removed is publishedNever

Address-only invitations never create an invited membership, so member lists do not reveal whether an address belongs to an existing account (DOM-008). JWT access tokens already issued remain verifiable offline until expiry; strict-online resources and introspection see the change immediately.

Caveats#

  • Offline-grant JWTs have no sid. Introspection and UserInfo cannot tie them to a session, so they check the user-level revocation record instead: after "logout everywhere" (and the other account-wide revocations in the table above) they are inactive at once (SES-005, TestLogoutEverywhereEndsOfflineTokens). Revocations that keep offline grants (the surviving account of an account merge, a received shared signal that does not cover offline access) record only their sessions, so offline-grant tokens stay active until expiry, like the grants they come from. Offline verifiers still accept every unexpired token. Because iat has one-second resolution, a token issued in the same second as an account-wide revocation is treated as issued before it.
  • Realm suspension does not affect introspection. Introspection keeps reporting unexpired tokens active; combine suspension with session revocation.
  • authz_version in a JWT is a hint. It means something only when compared to the value introspection returns.
  • Requests already authorized or in flight when a revocation commits complete (§13.2); long-running jobs or streams need their own re-checks.
  • The IdP does not enforce a resource's verification_mode; bounded_online is met only if the resource uses the SDKs' bounded-state mode (or an equivalent consumer of the session-event stream) with a staleness bound it chooses.
  • DPoP-bound tokens (cnf.jkt) are not enforced by the Go SDK verifier; see docs/protocols/oauth-oidc-profile.md.

Default lifetimes#

ArtifactDefaultConfiguration
Access token300 s per resource (30–3600 s)resource access_token_ttl_seconds, client token_policy.access_token_ttl_seconds, realm policy tokens.* limits (all tokens, service principals, single-factor sessions; tokens.md), cap tokens.max_access_token_ttl (60 min)
ID token5 mintokens.id_token_ttl
Authorization code60 stokens.authorization_code_ttl
Authentication transaction10 mintokens.transaction_ttl
IdP session30 min idle, 12 h absolutesessions.idle_timeout, sessions.absolute_timeout; realm/organization policy may shorten. A refresh of one of the session's grants is activity: it moves the idle expiry to the refresh family's window from now when that is later, and never shortens the session's own idle or absolute expiry (TestRefreshNeverShortensSession)
Remember-me session7 d idle, 30 d absolutesessions.remember_me_* (client must allow remember-me)
Offline grant7 d idle, 30 d absolutesessions.offline_* (client must allow offline_access)
Refresh family (session-bound)The idle window it was issued with (ordinary, remember-me or offline), kept across rotations; never beyond the session's absolute expiryClient token_policy.refresh_idle_seconds / refresh_absolute_seconds may shorten (ADR 0011)
Support grant and token≤ 5 minimpersonation.max_ttl, realm impersonation.max_ttl_seconds, tokens.impersonation_access_token_ttl_seconds and tokens.access_token_ttl_seconds (the smallest applies)
Support handoff code≤ 30 simpersonation.handoff_ttl
Email codes (verification, login, reset)10 mintokens.challenge_ttl
Invitation72 htokens.invitation_ttl
Device code10 mintokens.device_code_ttl
Pushed authorization request60 sfixed
Clock tolerance60 s server, 30 s Go SDKtokens.clock_skew; verifier.Config.ClockSkew
ProudAuth · Self-hosted identityBuilt 2026-10-13 · d9cc813ae809 · content snapshot
⌕

Search all guides and the API contract.

↑ ↓ to navigate Enter to open · Esc to close