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.
answer
- Four axes: latency, revocation, AS-coupling, confidentiality
- Opaque at edge, short JWT internally
- Introspect/token-exchange once at gateway
- Live introspect only for high-value ops
- Revocation window = JWT TTL + cache TTL (an SLA)
basics
~20 sUse 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.
solid answer
~50 sFor a many-service platform the decision hinges on latency, revocation immediacy, availability coupling, and operational load on the authorization server. Pure introspection gives central control and instant revocation but makes the AS a per-request dependency and bottleneck; pure JWTs scale and survive AS outages but can't be revoked before exp and leak claims. A pragmatic topology: issue opaque tokens to external clients (small, revocable, confidential), and at the edge/gateway exchange or introspect once, then propagate short-lived internal JWTs between services so hot internal paths do local validation with no round-trip. Reserve live introspection (or a shared denylist) for high-value operations that demand immediate revocation. Bound JWT lifetimes to the acceptable revocation window and pair with refresh tokens. Add introspection caching with an exp-bounded TTL as a revocation/performance dial. Standardize authority mapping, timeouts, and fail-closed behavior across services, and treat cache TTL as a documented security SLA.
code
java · 19 lines// One app serving both: introspect sensitive paths, JWT elsewhere.
@Bean
SecurityFilterChain chain(HttpSecurity http,
AuthenticationManagerResolver<HttpServletRequest> resolver) throws Exception {
http
.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(resolver));
return http.build();
}
@Bean
AuthenticationManagerResolver<HttpServletRequest> resolver(
OpaqueTokenIntrospector introspector, JwtDecoder jwtDecoder) {
AuthenticationManager opaque = new ProviderManager(
new OpaqueTokenAuthenticationProvider(introspector));
AuthenticationManager jwt = new ProviderManager(
new JwtAuthenticationProvider(jwtDecoder));
return request -> request.getServletPath().startsWith("/admin") ? opaque : jwt;
}go deeper
Not expected at this depth; know only that both formats exist with different trade-offs.
Should recognize edge-vs-internal differences and that mixing is possible.
Should propose opaque-at-edge / JWT-internal and mention AuthenticationManagerResolver plus caching.
Owns the four-axis framing, token-exchange topology, revocation-window SLA, AS capacity/observability, and standardized fail-closed policy across services.
## Framing the decision At platform scale the token format is an architecture trade among four axes: 1. **Latency / throughput** — JWT validates locally (CPU only); opaque needs a network `introspect` per request. 2. **Revocation immediacy** — opaque = instant (AS consulted each call); JWT = only at `exp` without extra machinery. 3. **Availability coupling** — JWT survives an AS outage (cached JWKs); opaque hard-depends on the introspection endpoint. 4. **Confidentiality & size** — opaque leaks nothing and is tiny; JWT payload is readable (base64url) and larger. A single global answer is usually wrong; different traffic classes have different needs. ## Case for opaque at the boundary External-facing tokens benefit most from opaque: they are held by clients you don't control, so **confidentiality** (no readable claims) and **instant revocation** (compromise, logout, deprovisioning) matter, and the small token size is nice on the wire. The AS remains the **single source of truth** for external access. ## Case for JWT internally East-west traffic between microservices is high-volume and latency-sensitive, and the services are inside your trust boundary. **Short-lived internal JWTs** let each service validate locally with zero round-trips and keep working if the AS blips — exactly the JWT strengths, with the revocation weakness bounded by a short lifetime. ## A best-of-both topology - **Edge/gateway** receives the client's **opaque** token and **introspects once** (or performs an RFC 8693 **token exchange**) to obtain a **short-lived internal JWT**. - Downstream services trust the internal JWT and validate it **locally** — no per-hop introspection. - **High-value operations** (payments, admin, key rotation) additionally introspect live or check a **shared denylist** so revocation is immediate where it counts. - **Introspection caching** at the edge with a TTL bounded by `exp` tunes AS load vs revocation latency. This concentrates the network dependency at one controlled tier, keeps the fan-out cheap, and preserves immediate revocation for the operations that justify its cost. ## Spring realization - Edge and sensitive services: `oauth2ResourceServer().opaqueToken(...)`, optionally behind a caching `OpaqueTokenIntrospector` bean. - Internal services: `oauth2ResourceServer().jwt(...)` with a shared issuer/JWK Set. - Mixed endpoints in one app: an **`AuthenticationManagerResolver<HttpServletRequest>`** to route some paths to introspection and others to JWT. - Standardize a **`Converter`/authority mapping** so `scope`/`roles` map consistently, and enforce uniform **timeouts + fail-closed** policy. ## Governance / operational concerns - **Revocation SLA**: publish the effective revocation window (JWT lifetime + cache TTL) as a security property, not an accident. - **Key management**: JWK rotation strategy and overlap windows for the internal JWTs. - **AS capacity planning**: introspection QPS from the edge; caching and token-exchange reduce it. - **Observability**: metrics on introspection latency/error rate, cache hit ratio, and 401 rates per tier. - **Blast radius**: fail-closed everywhere; circuit breakers so a degraded AS sheds rather than cascades. ## Anti-patterns - Introspecting on **every internal hop** (needless latency + AS load). - **Long-lived JWTs** everywhere with no revocation story. - Caching introspection **past `exp`** or logging raw tokens. - Assuming JWT payloads are secret.
- What is the effective revocation window in your topology, and how do you communicate it?It's the internal JWT lifetime plus any introspection cache TTL on paths that don't introspect live. You publish that number as a security SLA so operations and auditors know how fast a revocation propagates, and you shorten it (or introspect live) for high-value operations.
- How does RFC 8693 token exchange fit at the edge?The gateway swaps the client's opaque external token for a short-lived, audience-scoped internal JWT via the token-exchange grant, so downstream services validate locally without introspecting, while external revocation still bites at the edge.
- Why not just introspect on every service hop for maximum safety?It multiplies latency per request and turns the introspection endpoint into a platform-wide bottleneck and single point of failure. Concentrating introspection at the edge plus short internal JWTs gives nearly the same safety at a fraction of the cost.
saying these in an interview costs you the question
- Proposing a single token format for all traffic classes without trade-off analysis
- Introspecting on every internal microservice hop
- Long-lived JWTs with no revocation mechanism
- Treating cache TTL as a perf detail rather than a documented revocation SLA
- Assuming AS is always up (no fail-closed / circuit breaker)