A JWT lets a server authenticate a request by validating a signed token instead of looking up a session — so the API stays stateless. The token is a self-contained, cryptographically signed set of claims (subject, expiry, scopes); if the signature checks out and it isn't expired, the server trusts the claims without any database or session store hit.
The typical flow
- The client authenticates once (credentials, or an OAuth2 login) against whoever issues tokens.
- The issuer returns a signed JWT —
header.payload.signature, base64url-encoded. The payload carries claims likesub,exp,iss, andscope. - The client sends it on every request:
Authorization: Bearer eyJ.... - The API validates the token: checks the signature against the issuer's public key (or shared secret), the expiry, and the issuer/audience — no session lookup.
- Claims become the
Authentication(JwtAuthenticationToken), withscope/roles mapped to authorities.
Resource server: just validate
In Spring Security, the API is a resource server — it consumes tokens, it doesn't issue them. Configuration is tiny:
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
http
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()));
return http.build();
}
spring.security.oauth2.resourceserver.jwt.issuer-uri: https://auth.example.com
Spring fetches the issuer's JWKS (public keys) and validates every bearer token automatically. The BearerTokenAuthenticationFilter does the work.
Resource server vs authorization server
- Authorization server = the thing that issues tokens (handles login, runs the OAuth2 flows, signs JWTs). In the Spring world this is Spring Authorization Server (a separate project), or an external provider like Keycloak/Auth0/Okta.
- Resource server = your protected API that validates incoming tokens and serves data. Most services you build are resource servers.
Don't sign your own JWTs by hand in the API — let an authorization server issue them and have the resource server validate via the issuer's JWKS.