// Verify a CodeB JWT locally in Java (nimbus-jose-jwt). // Signed with RS256 against the tenant JWKS. European Digital Identity // Wallet id_tokens are validated the same way. // [jwt-validation-cookbook 2026-07-23] // // Maven: // // com.nimbusds // nimbus-jose-jwt // 9.37.3 // import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSourceBuilder; import com.nimbusds.jose.proc.JWSKeySelector; 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.URL; import java.util.Set; public class JwtVerify { private static final String ISSUER = "https://"; private static final String AUDIENCE = ""; public static JWTClaimsSet verify(String token) throws Exception { // JWKSourceBuilder caches keys in memory (default 5 min TTL) and // refreshes automatically on kid-miss. JWKSource keys = JWKSourceBuilder .create(new URL(ISSUER + "/.well-known/jwks.json")) .build(); ConfigurableJWTProcessor p = new DefaultJWTProcessor<>(); JWSKeySelector sel = new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, keys); p.setJWSKeySelector(sel); p.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>( AUDIENCE, new JWTClaimsSet.Builder().issuer(ISSUER).build(), Set.of("sub", "iat", "exp") )); return p.process(token, null); // verifies sig + iss + aud + exp } public static void main(String[] args) throws Exception { JWTClaimsSet c = verify(args[0]); System.out.println("sub = " + c.getSubject()); System.out.println("role = " + c.getClaim("role")); } } // RFCs: 7519 (JWT), 7517 (JWK), 7518 (JWA).