Skip to main content

JWT

A JSON Web Token is a signed, self-contained credential. The resource server validates it locally by checking the signature, so no lookup against the issuer is needed on each request.

Structure

Three base64url-encoded parts separated by dots:

eyJhbGciOiJSUzI1NiIsImtpZCI6ImU3N2E1MTI5In0 . eyJzdWIiOiJ1NWVyIiwiZXhwIjoxNzAwMH0 . MEUCIQD...
└──────────── header ────────────┘ └──────── payload ────────┘ └── signature ──┘
  • Header — signing algorithm (alg) and key identifier (kid).
  • Payload — the claims: sub, iss, aud, exp, iat, plus whatever the application adds.
  • Signature — over the encoded header and payload.

The first two parts are encoded, not encrypted. Anyone holding the token can read every claim, so never put anything confidential in the payload.

Flow

  1. The client submits credentials to the authentication endpoint.
  2. The server verifies them — Spring Security, or an external identity provider.
  3. On success it issues a signed token.
  4. The client stores the token and sends it on each subsequent request as Authorization: Bearer <token>.
  5. The server validates the signature and the claims. Because the token is signed and carries an expiry, no server-side session is needed.

Validating

As a resource server, most of this is configuration rather than code:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
# Discovers the JWK set from the issuer's metadata document
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://127.0.0.1:8080

# Or point at the key set directly
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://127.0.0.1:8080/oauth2/jwks
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}

Spring fetches and caches the JWK set, selects the key by kid, and validates the signature, exp, nbf and iss. Audience validation must be added explicitly — it is not on by default, and without it a token minted for another service in the same realm is accepted.

Libraries

LibraryNotes
spring-security-oauth2-joseWhat the resource server starter uses. Nothing extra needed if you are already on Spring Security.
Nimbus JOSE + JWTThe implementation underneath it; use directly for fine-grained control.
jjwtSmall, fluent API, popular for hand-rolled token issuance.
java-jwt (Auth0)Also widely used; more verbose than jjwt.

Prefer the Spring Security integration over issuing tokens by hand. Hand-rolled issuance tends to omit aud, iss or key rotation, and each omission is a real weakness rather than a stylistic one.

Practical cautions

  • Tokens cannot be revoked. A signed token is valid until it expires. Keep access tokens short-lived — minutes — and use refresh tokens for longevity. Where immediate revocation is required, use opaque tokens with introspection instead.
  • Always pin the algorithm. A validator that trusts the header's alg can be attacked with alg: none or by an RS256 token re-signed as HS256 using the public key as the HMAC secret. Spring Security's validator pins it; a hand-written one must too.
  • Send the token intact. The signature covers header and payload exactly as encoded; any re-encoding invalidates it.
  • Storage in browsers is the hard part. localStorage is readable by any XSS payload; cookies are safer with HttpOnly, Secure and SameSite set, but then need CSRF protection. There is no option without a trade-off.
  • Size. Tokens travel on every request. Large claim sets inflate every header, and some proxies cap header size at 8 KB.