skip to content

Resource Server: Opaque Tokens

An opaque token carries no claims, so the resource server introspects it at the provider on every request — buying instant revocation at the cost of a network call. The JWT-versus-opaque trade-off is a reliable interview question.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is an opaque token in a Spring Security resource server, and how do you configure the resource server to validate one?

level: juniorimportance: must knowfreq 62%

answer

  1. Random string, no local claims
  2. RFC 7662 introspection POST
  3. oauth2ResourceServer().opaqueToken()
  4. introspection-uri + client-id/secret
  5. active:true -> BearerTokenAuthentication

basics

~10 s

An opaque token is a random string with no readable content. The resource server can't decode it, so it calls the authorization server's introspection endpoint to check it. You enable it with oauth2ResourceServer().opaqueToken().

solid answer

~40 s

An opaque token (also called a reference token) is an unguessable random string that carries no self-contained claims — unlike a JWT you cannot decode it locally. To validate it, the resource server must ask the authorization server whether it is active by calling the RFC 7662 token-introspection endpoint. In Spring Security you enable this on the SecurityFilterChain with http.oauth2ResourceServer(o -> o.opaqueToken(Customizer.withDefaults())). You then supply the introspection URI and client credentials via spring.security.oauth2.resourceserver.opaquetoken.* properties. Spring auto-configures a SpringOpaqueTokenIntrospector (a NimbusOpaqueTokenIntrospector under the hood) that POSTs the token to the endpoint and, on an active response, builds an authenticated BearerTokenAuthentication that populates the SecurityContext. An inactive or unknown token yields 401 Unauthorized.

code

java · 19 lines
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .opaqueToken(Customizer.withDefaults()));
        return http.build();
    }
}

// application.yml
// spring.security.oauth2.resourceserver.opaquetoken:
//   introspection-uri: https://auth.example.com/oauth2/introspect
//   client-id: my-resource-server
//   client-secret: ${INTROSPECT_SECRET}

go deeper

for a junior

Know the definition (random string, can't be decoded), that a network introspection call validates it, and the one-line oauth2ResourceServer().opaqueToken() config plus the three properties.

for a middle

Should name RFC 7662, the active flag, SpringOpaqueTokenIntrospector auto-config, and that scopes become SCOPE_ authorities on a BearerTokenAuthentication.

for a senior

Should discuss the authenticated introspection call, per-request latency, and 401 mapping paths.

for a principal

Frames it as a source-of-truth / revocation trade-off against JWT for the system's auth topology.

## What an opaque token is A **bearer token** sent in the `Authorization: Bearer <token>` header can be one of two shapes: - **Self-contained (JWT):** a signed JSON payload the resource server can validate and read *locally* just by verifying the signature. - **Opaque (a.k.a. reference token):** an unguessable random string (e.g. `a1b2c3...`) that carries **no readable content**. The resource server cannot decode it or learn who the user is by looking at it. The only way to find out if it is valid and what it grants is to **ask the authorization server**. ## RFC 7662 introspection The standard mechanism for that lookup is **OAuth 2.0 Token Introspection (RFC 7662)**. The resource server sends an HTTP `POST` to the authorization server's **introspection endpoint** with `token=<the-opaque-token>`, authenticating itself with client credentials. The endpoint replies with JSON; the key field is `"active": true|false`. If active, the response also typically includes `sub`, `scope`, `exp`, `client_id`, etc. ## Spring Security configuration On your `SecurityFilterChain` bean: ```java http.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(Customizer.withDefaults())); ``` Then provide the endpoint and this resource server's introspection credentials in `application.yml`: ```yaml spring: security: oauth2: resourceserver: opaquetoken: introspection-uri: https://auth.example.com/oauth2/introspect client-id: my-resource-server client-secret: ${INTROSPECT_SECRET} ``` Given these properties, Spring Boot auto-configures a bean of type **`OpaqueTokenIntrospector`** — specifically **`SpringOpaqueTokenIntrospector`** (which delegates to Nimbus). The `BearerTokenAuthenticationFilter` extracts the token, an `OpaqueTokenAuthenticationProvider` calls the introspector, and on success a **`BearerTokenAuthentication`** is placed in the `SecurityContext`. Its `getName()` is the `sub` claim and its authorities are derived from the `scope` claim (prefixed `SCOPE_`). ## Gotchas - You must supply **`introspection-uri`** *and* `client-id`/`client-secret` — introspection is an authenticated call; the resource server is itself an OAuth client to the endpoint. - `opaqueToken()` and `jwt()` are mutually distinct paths. If you configure both, Spring needs an `AuthenticationManagerResolver` to decide which to use per request — you can't silently accept either shape by default. - An **inactive** token (`active:false`), an expired token, or a network failure to the endpoint results in a **401**. - Every request incurs a **network round-trip** unless you cache — this is the main cost versus JWT. ## When to use Choose opaque tokens when you need **instant revocation** and tight central control (the authorization server is the single source of truth on every call), and you accept the per-request introspection latency.

  • Why does the introspection call itself need client credentials?
    RFC 7662 requires the caller to authenticate so the authorization server only reveals token details to trusted resource servers. The resource server acts as an OAuth client to the introspection endpoint, sending its client-id/client-secret (typically HTTP Basic).
  • What HTTP status does the client get if the token is inactive or the introspection endpoint is unreachable?
    401 Unauthorized. An inactive/unknown token produces an OAuth2 'invalid_token' error; an endpoint failure surfaces as an authentication error, both mapped to 401 by BearerTokenAuthenticationEntryPoint.

saying these in an interview costs you the question

  • Claiming the resource server can decode an opaque token to read the subject or scopes (it cannot — it has no readable content)
  • Saying introspection is anonymous / needs no credentials
  • Confusing opaque-token config with jwt() decoding

context

open as a page

What is the OpaqueTokenIntrospector interface, and how does Spring use it to turn an opaque token into an authenticated principal?

level: middleimportance: must knowfreq 55%

basics

~10 s

OpaqueTokenIntrospector has one method, introspect(token), that calls the introspection endpoint and returns an OAuth2AuthenticatedPrincipal with the token's attributes and authorities. Spring's OpaqueTokenAuthenticationProvider calls it and builds a BearerTokenAuthentication.

open as a page

Compare opaque introspected tokens with self-contained JWTs for a resource server. What are the trade-offs, and when would you pick each?

level: seniorimportance: must knowfreq 58%

basics

~20 s

JWTs are validated locally (fast, no network) but can't be revoked until they expire and expose claims to anyone. Opaque tokens require a network introspection call per request (slower, central dependency) but give instant revocation and keep claims private. Choose opaque when revocation matters, JWT for scale/latency.

open as a page

Introspection adds a network call to every request. How do you design a Spring resource server to keep opaque-token validation performant, and what does that cost you?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Wrap the OpaqueTokenIntrospector in a cache keyed by the token so repeated requests skip the introspection call. Bound the cache by a short TTL and the token's exp. The cost is that a revoked token stays accepted until its cache entry expires.

open as a page

You're setting token strategy for a platform of many microservices behind one authorization server. Argue for opaque introspected tokens vs JWTs, and describe a topology that gets the best of both.

level: principalimportance: should knowfreq 26%

basics

~20 s

Use short-lived JWTs internally for low-latency, no-network validation, and opaque tokens (with introspection) where instant revocation or claim confidentiality matters. A common topology: opaque tokens at the edge, exchanged for internal JWTs, with introspection or a denylist guarding sensitive operations.

open as a page