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 ( 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_tokenorrefresh_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 OKwith 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 |
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 |
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.