JWT & OAuth2
Spring Security OAuth2 Resource Server — Protecting APIs with Tokens
Validating external access tokens (like Okta/Keycloak JWTs) in Spring security filters.
Introduction
In modern distributed systems, authentication is decoupled. Rather than verifying database passwords, your APIs act as **OAuth2 Resource Servers**. They receive access tokens (JWTs) in the header of incoming requests, validate the token's cryptographic signature using public keys retrieved from the authorization server, and extract scopes and claims to perform authorization.
Why It Matters
Duplicating user authentication logic across dozens of microservices leads to security leaks and codebase bloat. Acting as an OAuth2 Resource Server delegates the burden of authentication to a centralized service (like Keycloak, Okta, or AWS Cognito). Microservices just focus on verifying incoming tokens, making them secure, decoupled, and lightweight.
Real-World Analogy
Think of the Resource Server like a **flight attendant checking boarding passes** at an airplane door. The flight attendant does not perform background checks or issue passports. They simply inspect the barcode of the boarding pass (JWT), scan it to verify it was signed by the official airline (Authorization Server), check if your flight number matches this flight (aud claim), and direct you to your seat.
Detailed Mechanics
1. JSON Web Key Sets (JWKS) Integration
When a Resource Server starts up:
- It contacts the authorization server's JSON Web Key Set (JWKS) endpoint.
- It downloads the server's public key certificates and caches them in memory.
- When a client requests access with a JWT, the Resource Server reads the Key ID (
kid) from the token header, selects the matching public key, and verifies the signature offline.
2. Scope-Based Authorization
By default, Spring Security maps the token's scope claim directly to authorities prefixed with SCOPE_. For example, if a token contains scope: "read write", Spring Security maps this to SCOPE_read and SCOPE_write, allowing developers to enforce checks like hasAuthority("SCOPE_read").
Practical Example
The configuration below integrates Okta JWK endpoints in properties and maps custom JWT roles to Spring security authorities:
The Java security filter configuration using a custom JWT converter:
Quick Quiz
Q1: Why is it beneficial to configure both issuer-uri and jwk-set-uri in properties?
A) To allow testing database triggers locally.
B) Setting issuer-uri guarantees the API validates that the issuer claim inside the JWT matches the expected Identity Provider domain, while jwk-set-uri supplies the path to download validation keys.
C) It is required to enable TLS 1.3 encryption.
D) So that Spring Boot can perform HTML template styling.
Answer: B — Setting both ensures both signature validity (via JWKS) and origin authorization validity (via Issuer checks) are verified.
Q2: By default, if a token scope is read, what role or authority does Spring Security map it to?
A) read
B) ROLE_READ
C) SCOPE_read
D) Scope:read
Answer: C — Spring Security prefixes authorization scopes with SCOPE_ to differentiate them from custom client roles.
Scenario-Based Challenge
Production Scenario:
Your resource server application starts up in a Docker container, but it crashes on startup with a connection timeout error because it cannot query the Identity Provider's jwk-set-uri before the network bridge initializes. How do you resolve this?
When issuer-uri is defined, Spring Security's startup validation attempts to contact the identity provider's well-known discovery endpoint.
To bypass startup dependencies:
- Remove the
issuer-uriproperty fromapplication.propertiesand keep onlyjwk-set-uri. This prevents startup HTTP checks. - Customize the
JwtDecoderbean to use a lazy connection resolver or configure connection timeouts to prevent startup initialization freezes.
Interview Questions
1. Conceptual: How does a Resource Server verify a JWT signature if the Authorization Server rotated its private key?
The Resource Server extracts the Key ID (kid) from the incoming JWT header. It checks its locally cached JWK set. If the key is not in the cache, it sends a query to the JWKS endpoint to download the rotated public keys, updates its cache, and verifies the signature.
2. Concept: Why should you validate the 'aud' (Audience) claim inside a Resource Server?
The aud claim specifies the intended recipient of the token. If an attacker obtains a JWT issued for Client A and passes it to your Resource Server (Client B), validating the aud claim ensures you reject it because the token was not issued for your API service boundary.
Production Considerations
Implement a local cache for the public keys downloaded from the JWKS endpoint. Set an expiration threshold (e.g. 24 hours) for the cache. Ensure that connection timeouts for JWKS queries are configured to prevent server threads from blocking if the Identity Provider goes offline.