// 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).