Opaque Tokens, Introspection and Revocation

This section documents OAuth 2.0 (RFC 6749) as amended by the OAuth 2.0 Security Best Current Practice (RFC 9700 / BCP 240), the OAuth 1.0 Protocol (RFC 5849) for historical context, and OpenID Connect Core 1.0, as published at the IETF Datatracker and the OpenID Foundation specifications — and, on the Spring pages, Spring Boot 4.1.x and Spring Security 7.1.x as published at the Spring Security reference documentation — which are the references these pages are written and verified against.

OAuth 2.1 is still an Internet-Draft (draft-ietf-oauth-v2-1-16, 3 September 2026) and is flagged as such everywhere it appears on these pages. It is a working-group consolidation in progress, not a published standard; nothing here should be read as saying otherwise.

This content was generated with the assistance of AI and should be verified against those official specifications before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

An opaque access token is a random string with no extractable structure — a database handle, effectively — that means nothing to anyone except the authorization server that issued it. This page covers how a resource server validates one (introspection, RFC 7662), how a token gets invalidated on demand (revocation, RFC 7009), and closes with the decision that most deployments actually have to make: JWT or opaque, and when to mix the two.

RFC 7662: token introspection

Where a JWT-formatted token is validated locally against a cached JWKS (see JWT and the JOSE Family), an opaque token carries nothing to validate locally — the resource server must ask the authorization server whether the token is currently valid, and for what. That is introspection.

Request

POST /oauth2/introspect HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic cnM6cnMtc2VjcmV0

token=mF_9.B5f-4.1JqM&
token_type_hint=access_token
  • token (required) — the string the client presented as its access token.

  • token_type_hint (optional) — access_token or refresh_token; a hint that lets the authorization server check the right token store first, not a guarantee it will only check that one.

  • The introspection endpoint is itself an OAuth-protected resource: the caller (the resource server, acting as an introspection client) authenticates to it — Basic, private_key_jwt, mTLS, whatever the deployment’s client authentication policy requires (see Client Credentials and Client Authentication). A resource server must never expose an unauthenticated introspection proxy, since the response can include sensitive claims about the resource owner.

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "active": true,
  "scope": "orders:read orders:write",
  "client_id": "s6BhdRkqt3",
  "username": "alice",
  "token_type": "Bearer",
  "exp": 1758003600,
  "iat": 1758000000,
  "sub": "alice",
  "aud": "https://api.example.com",
  "iss": "https://as.example.com"
}

active is the only field every caller must check first: false (or the whole set of extra fields simply absent) means the token is not currently valid, for any reason — expired, revoked, never issued, or malformed — and RFC 7662 §2.2 deliberately does not distinguish which, so the introspection response itself cannot be used to enumerate valid token identifiers. Every other field mirrors what a self-contained JWT would have carried in its claims, letting a resource server that supports both formats treat the two uniformly once validated.

Caching

Calling introspection on every single request adds a network round trip (and a hard dependency on the authorization server’s availability) to every API call. Resource servers typically cache a positive introspection result for a short, deliberately bounded TTL (seconds, not minutes) — short enough that a revocation still takes effect promptly, long enough to absorb request bursts from the same token without hammering the introspection endpoint. A negative result (active: false) is safe to cache more aggressively, since a token that is not valid now will not become valid later.

RFC 9701: JWT introspection responses

Plain JSON introspection responses are not signed — if the transport between resource server and authorization server is not fully trusted (an intermediary proxy, a multi-tenant AS shared across security domains), the response’s own integrity and audience cannot be verified the way a JWT’s can. RFC 9701 lets the authorization server return the introspection result as a signed (optionally encrypted) JWT instead of plain JSON — Accept: application/token-introspection+jwt on the request, application/token-introspection+jwt on the response — giving the same tamper-evidence and audience-restriction guarantees introspection otherwise lacks.

RFC 7009: token revocation

Revocation is the other half of the lifecycle: telling the authorization server "this token (and, for refresh_token, everything derived from it) should stop being valid now", independent of exp.

Request

POST /oauth2/revoke HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=45ghiukldjahdnhzdauz&
token_type_hint=refresh_token
  • token (required) — the token to revoke.

  • token_type_hint (optional) — as with introspection, an optimization hint, not a restriction; the authorization server checks other token types too if the hinted one does not match (RFC 7009 §2.1).

  • A successful revocation returns 200 OK with an empty body, whether or not the token was valid to begin with — RFC 7009 §2.2 makes this deliberate, again to avoid letting the endpoint be used to probe which tokens exist.

HTTP/1.1 200 OK

What revoking a refresh token does

Revoking a refresh token is specified to invalidate the whole grant it came from: the refresh token itself, and, at the authorization server’s discretion, every access token already issued from it (RFC 7009 §2.1). This is the same mechanism Access and Refresh Tokens describes for reuse-detected refresh-token families — revocation is that same "kill the family" operation, just triggered deliberately (e.g. "log out everywhere", "user reported this device stolen") rather than by detected replay.

What revoking an access token does — and does not — reach

Revoking an opaque access token is immediate and complete: the very next introspection call returns active: false, because introspection is the live check.

Revoking a JWT access token run through this same endpoint only marks it revoked in the authorization server’s own records — it does not retroactively change what a resource server that validates the JWT locally will do, because that resource server never asks the authorization server anything. A revoked JWT keeps validating successfully, everywhere it is checked purely locally, until exp. This is the exact limitation already flagged on JWT and the JOSE Family: revocation only reaches a locally-validated JWT if something else bridges the gap — introspection used alongside the JWT (defeating much of the point of using a JWT), a short exp, or a deny-list the resource server also consults.

Token status lists

draft-ietf-oauth-status-list defines a compact, bitmap-based Token Status List an authorization server can publish and a verifier can fetch and cache cheaply, to check the revocation status of many by-value tokens (JWTs or similar) without a per-token network round trip. Each token carries a status_list claim pointing at an index into a published list; the verifier looks up that one bit locally against its cached copy of the list. It is a middle ground between "no revocation check at all" and "introspect every request" — still in progress as an Internet-Draft, but the direction the ecosystem is heading in for bridging revocation into by-value tokens without paying introspection’s full latency cost on every call.

Session vs. token lifetime

A session (the resource owner’s authenticated relationship with the authorization server, often backed by a browser cookie) and a token’s lifetime are related but distinct clocks. Ending a session (logout) does not automatically revoke every access and refresh token already issued during it, unless the authorization server is explicitly built to cascade that — see Logout and Session Management for RP-initiated and back-channel logout, which is exactly the mechanism that closes this gap for OpenID Connect deployments.

Decision table: JWT vs. opaque

Dimension JWT (by-value) Opaque (by-reference)

Latency

No network call to validate; verification is local (signature + cached JWKS)

One round trip to the introspection endpoint per validation, mitigated by short-TTL caching

Revocation immediacy

Cannot be revoked before exp, unless bridged by a status list or a deny-list check

Immediate — the very next introspection call reflects the revoked state

Privacy

Claims are readable by anyone holding the token (base64url, not encrypted) unless JWE-wrapped

No claims travel with the token itself; only the authorization server (and whoever it responds to) sees them

PII in the token

Whatever is put in the payload rides along with every copy of the token, in every log line that captures it

None — PII stays server-side, returned only in a (typically short-lived, cached) introspection response

Multi-audience

A single JWT can carry a multi-value aud and be validated independently by several resource servers with no coordination beyond sharing the JWKS

Every resource server needs its own network path (and usually its own client credentials) to the introspection endpoint

Operational coupling

Resource server can validate even if the authorization server is briefly unreachable, once it has a cached JWKS

Resource server has a hard runtime dependency on the authorization server’s introspection endpoint being reachable

Neither column wins outright — a deployment with strict "logout must work everywhere within seconds" requirements leans opaque (or a short-exp JWT bridged by introspection/status lists); a deployment fanning out to many independent, high-throughput resource servers leans JWT. Mixed deployments are common: short-lived JWTs for routine calls, with a status-list or introspection check added specifically for higher-risk operations.

Spring Security implements the opaque side of this out of the box via OpaqueTokenIntrospector (http.oauth2ResourceServer(o → o.opaqueToken(…​)), spring.security.oauth2.resourceserver.opaque-token.*); see Spring Security — Opaque tokens rather than reimplementing the introspection call by hand.

RS introspects vs. RS validates locally

sequenceDiagram participant C as Client participant RS as Resource server participant AS as Authorization server rect rgb(238, 247, 238) Note over C,AS: Opaque token -- RS introspects on every call C->>RS: GET /orders, Authorization: Bearer opaque-token RS->>AS: POST /oauth2/introspect, token=opaque-token AS-->>RS: active=true, scope, sub, exp RS-->>C: 200 OK end rect rgb(238, 244, 250) Note over C,AS: JWT access token -- RS validates locally C->>RS: GET /orders, Authorization: Bearer jwt-token Note over RS: Verify signature against cached JWKS\ncheck iss, aud, exp -- no call to AS RS-->>C: 200 OK end