ReviseAlgo Logo

JWT & OAuth2

Spring Security OAuth2 Resource Server — Protecting APIs with Tokens

Validating external access tokens (like Okta/Keycloak JWTs) in Spring security filters.

Last Updated: June 14, 2026 12 min read

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:

  1. It contacts the authorization server's JSON Web Key Set (JWKS) endpoint.
  2. It downloads the server's public key certificates and caches them in memory.
  3. 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?

View Solution

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:

  1. Remove the issuer-uri property from application.properties and keep only jwk-set-uri. This prevents startup HTTP checks.
  2. Customize the JwtDecoder bean 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.