Skip to content
ecdsa.com

Verify a JWT with JWKS · Java

How to verify a JWT with a JWKS in Java

To verify a JWT against a JWKS in Java, Nimbus JOSE + JWT combines a cached remote JWKSource, a key selector that accepts only ES256, and a claims verifier for audience, issuer and required claims in one DefaultJWTProcessor. The run serves a real JWKS over HTTPS inside the container and checks five tokens: one valid and four rejected.

Tested with com.nimbusds:nimbus-jose-jwt 10.10 (OpenJDK 25.0.4.1 (Temurin)).

JwtVerifier.java — remote JWK source, ES256 key selector, claims verifier
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import java.net.URI;
import java.util.Set;
public class JwtVerifier {
private final ConfigurableJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
public JwtVerifier() throws Exception {
// Cached, refreshed on a schedule and when a token names an unknown kid
JWKSource<SecurityContext> keys = JWKSourceBuilder
.create(URI.create("https://issuer.example.com/.well-known/jwks.json").toURL())
.retrying(true)
.build();
// Only ES256: the token's alg header must match, and the key is chosen by kid
processor.setJWSKeySelector(new JWSVerificationKeySelector<>(JWSAlgorithm.ES256, keys));
processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
"api://orders", // required audience
new JWTClaimsSet.Builder().issuer("https://issuer.example.com").build(), // exact-match claims
Set.of("sub", "exp"))); // required claims
}
public JWTClaimsSet verify(String token) throws Exception {
return processor.process(token, null); // throws BadJOSEException with the reason
}
}
Main.java — check every token in tokens.txt
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
JwtVerifier verifier = new JwtVerifier();
for (String line : Files.readAllLines(Path.of("tokens.txt"))) {
String[] parts = line.split(" ");
try {
System.out.println(parts[0] + ": accepted, sub=" + verifier.verify(parts[1]).getSubject());
} catch (Exception e) {
System.out.println(parts[0] + ": rejected: " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
}

How it works

  1. JWKSourceBuilder.create(url) builds a source that caches the JWKS for 5 minutes, refreshes it ahead of expiry, and refetches when a token names an unknown kid — at most once per 30 seconds. retrying(true) retries a failed fetch once.
  2. JWSVerificationKeySelector(JWSAlgorithm.ES256, keys) accepts only ES256 tokens and returns the key whose kid and type fit.
  3. DefaultJWTClaimsVerifier(audience, exactMatchClaims, requiredClaims) checks aud, iss and that sub and exp are present; exp and nbf are always checked when present.
  4. process returns the JWTClaimsSet or throws a BadJOSEException subclass that says what failed.

Gotchas

  • The default claims verifier checks only time claims: with just a key selector, the token for api://billing is accepted by the orders API. Always set a DefaultJWTClaimsVerifier with the audience and issuer.
  • An unknown kid and a token for another algorithm produce the same message, Signed JWT rejected: Another algorithm expected, or no matching key(s) found — compare the token header with the JWKS to tell which.
  • The JVM uses its own trust store (cacerts), not the operating system's. An issuer with a private CA needs that CA imported there, or a custom ResourceRetriever with its own SSLContext.
  • Spring Security's resource server wraps this processor (NimbusJwtDecoder). With issuer-uri it finds the JWKS through the issuer's discovery document and validates iss — the audience still has to be configured, for the same reason as here.

Related recipes