A comprehensive Java implementation of JSON Web Token (JWT) with creation, signing, and verification capabilities for server-side JVM applications.
—
Dynamic key resolution interfaces supporting key rotation, multi-tenant scenarios, and advanced key management patterns for RSA and ECDSA algorithms.
Generic interface for providing cryptographic keys with support for key identification and dynamic resolution.
/**
* Generic key provider interface for dynamic key resolution
* @param <U> Public key type (RSAPublicKey or ECPublicKey)
* @param <R> Private key type (RSAPrivateKey or ECPrivateKey)
*/
public interface KeyProvider<U, R> {
/**
* Getter for a Public Key instance using the received Key Id.
* @param keyId the Key Id specified in the JWT header
* @return a public key instance for verification or null if not found
*/
U getPublicKeyById(String keyId);
/**
* Getter for a Private Key instance used for signing JWTs.
* @return a private key instance for signing or null if not available
*/
R getPrivateKey();
/**
* Getter for the Id of the Private Key used for signing JWTs.
* @return the key id of the private key or null if not specified
*/
String getPrivateKeyId();
}Usage Examples:
import com.auth0.jwt.interfaces.KeyProvider;
import java.security.interfaces.RSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
// Custom key provider implementation
public class CustomKeyProvider implements KeyProvider<RSAPublicKey, RSAPrivateKey> {
private Map<String, RSAPublicKey> publicKeys;
private RSAPrivateKey privateKey;
private String privateKeyId;
public CustomKeyProvider(Map<String, RSAPublicKey> publicKeys,
RSAPrivateKey privateKey,
String privateKeyId) {
this.publicKeys = publicKeys;
this.privateKey = privateKey;
this.privateKeyId = privateKeyId;
}
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
return publicKeys.get(keyId);
}
@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
}Specialized key provider interface for RSA cryptographic operations.
/**
* RSA Key Provider interface for providing RSA keys for JWT signing and verification.
* Extends the generic KeyProvider with RSA-specific key types.
*/
public interface RSAKeyProvider extends KeyProvider<RSAPublicKey, RSAPrivateKey> {
/**
* Getter for an RSA Public Key instance using the received Key Id.
* @param keyId the Key Id specified in the JWT header ("kid" claim)
* @return an RSAPublicKey instance for verification or null if not found
*/
RSAPublicKey getPublicKeyById(String keyId);
/**
* Getter for an RSA Private Key instance used for signing JWTs.
* @return an RSAPrivateKey instance for signing or null if not available
*/
RSAPrivateKey getPrivateKey();
/**
* Getter for the Id of the RSA Private Key used for signing JWTs.
* @return the key id of the private key or null if not specified
*/
String getPrivateKeyId();
}Usage Examples:
import com.auth0.jwt.interfaces.RSAKeyProvider;
import com.auth0.jwt.algorithms.Algorithm;
import java.security.interfaces.RSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.util.Map;
import java.util.HashMap;
// Simple RSA key provider implementation
public class SimpleRSAKeyProvider implements RSAKeyProvider {
private final Map<String, RSAPublicKey> publicKeys;
private final RSAPrivateKey privateKey;
private final String privateKeyId;
public SimpleRSAKeyProvider(RSAPrivateKey privateKey, String privateKeyId,
Map<String, RSAPublicKey> publicKeys) {
this.privateKey = privateKey;
this.privateKeyId = privateKeyId;
this.publicKeys = new HashMap<>(publicKeys);
}
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
return publicKeys.get(keyId);
}
@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
}
// Usage with Algorithm
Map<String, RSAPublicKey> publicKeys = new HashMap<>();
publicKeys.put("key-1", rsaPublicKey1);
publicKeys.put("key-2", rsaPublicKey2);
RSAKeyProvider keyProvider = new SimpleRSAKeyProvider(
rsaPrivateKey,
"key-1",
publicKeys
);
Algorithm algorithm = Algorithm.RSA256(keyProvider);
// Create JWT with key ID in header
String token = JWT.create()
.withKeyId("key-1")
.withIssuer("auth0")
.sign(algorithm);
// Verify JWT - key provider will be used to resolve public key by key ID
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);Specialized key provider interface for Elliptic Curve Digital Signature Algorithm operations.
/**
* ECDSA Key Provider interface for providing EC keys for JWT signing and verification.
* Extends the generic KeyProvider with ECDSA-specific key types.
*/
public interface ECDSAKeyProvider extends KeyProvider<ECPublicKey, ECPrivateKey> {
/**
* Getter for an EC Public Key instance using the received Key Id.
* @param keyId the Key Id specified in the JWT header ("kid" claim)
* @return an ECPublicKey instance for verification or null if not found
*/
ECPublicKey getPublicKeyById(String keyId);
/**
* Getter for an EC Private Key instance used for signing JWTs.
* @return an ECPrivateKey instance for signing or null if not available
*/
ECPrivateKey getPrivateKey();
/**
* Getter for the Id of the EC Private Key used for signing JWTs.
* @return the key id of the private key or null if not specified
*/
String getPrivateKeyId();
}Usage Examples:
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.algorithms.Algorithm;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// ECDSA key provider with thread-safe key storage
public class ConcurrentECDSAKeyProvider implements ECDSAKeyProvider {
private final Map<String, ECPublicKey> publicKeys;
private final ECPrivateKey privateKey;
private final String privateKeyId;
public ConcurrentECDSAKeyProvider(ECPrivateKey privateKey, String privateKeyId) {
this.privateKey = privateKey;
this.privateKeyId = privateKeyId;
this.publicKeys = new ConcurrentHashMap<>();
}
public void addPublicKey(String keyId, ECPublicKey publicKey) {
publicKeys.put(keyId, publicKey);
}
public void removePublicKey(String keyId) {
publicKeys.remove(keyId);
}
@Override
public ECPublicKey getPublicKeyById(String keyId) {
return publicKeys.get(keyId);
}
@Override
public ECPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
}
// Usage with ECDSA algorithm
ECDSAKeyProvider keyProvider = new ConcurrentECDSAKeyProvider(ecPrivateKey, "ec-key-1");
keyProvider.addPublicKey("ec-key-1", ecPublicKey1);
keyProvider.addPublicKey("ec-key-2", ecPublicKey2);
Algorithm algorithm = Algorithm.ECDSA256(keyProvider);
// Create and verify JWT
String token = JWT.create()
.withKeyId("ec-key-1")
.withSubject("user123")
.sign(algorithm);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);Common patterns for implementing sophisticated key management scenarios.
Key Rotation Support:
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;
public class RotatingRSAKeyProvider implements RSAKeyProvider {
private final Map<String, KeyWithExpiry> publicKeys;
private volatile RSAPrivateKey currentPrivateKey;
private volatile String currentPrivateKeyId;
private static class KeyWithExpiry {
final RSAPublicKey key;
final Instant expiresAt;
KeyWithExpiry(RSAPublicKey key, Instant expiresAt) {
this.key = key;
this.expiresAt = expiresAt;
}
boolean isExpired() {
return Instant.now().isAfter(expiresAt);
}
}
public RotatingRSAKeyProvider() {
this.publicKeys = new ConcurrentHashMap<>();
}
public void addPublicKey(String keyId, RSAPublicKey key, Instant expiresAt) {
publicKeys.put(keyId, new KeyWithExpiry(key, expiresAt));
}
public void rotatePrivateKey(RSAPrivateKey newPrivateKey, String newKeyId) {
this.currentPrivateKey = newPrivateKey;
this.currentPrivateKeyId = newKeyId;
}
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
KeyWithExpiry keyWithExpiry = publicKeys.get(keyId);
if (keyWithExpiry == null || keyWithExpiry.isExpired()) {
return null;
}
return keyWithExpiry.key;
}
@Override
public RSAPrivateKey getPrivateKey() {
return currentPrivateKey;
}
@Override
public String getPrivateKeyId() {
return currentPrivateKeyId;
}
// Cleanup expired keys
public void cleanupExpiredKeys() {
publicKeys.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
}Remote Key Fetching:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.util.Base64;
public class RemoteRSAKeyProvider implements RSAKeyProvider {
private final HttpClient httpClient;
private final String jwksUrl;
private final Map<String, RSAPublicKey> keyCache;
private final RSAPrivateKey privateKey;
private final String privateKeyId;
public RemoteRSAKeyProvider(String jwksUrl, RSAPrivateKey privateKey, String privateKeyId) {
this.httpClient = HttpClient.newHttpClient();
this.jwksUrl = jwksUrl;
this.keyCache = new ConcurrentHashMap<>();
this.privateKey = privateKey;
this.privateKeyId = privateKeyId;
}
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
// Check cache first
RSAPublicKey cachedKey = keyCache.get(keyId);
if (cachedKey != null) {
return cachedKey;
}
// Fetch from remote JWKS endpoint
try {
RSAPublicKey fetchedKey = fetchPublicKeyFromJwks(keyId);
if (fetchedKey != null) {
keyCache.put(keyId, fetchedKey);
}
return fetchedKey;
} catch (Exception e) {
// Log error and return null
System.err.println("Failed to fetch public key " + keyId + ": " + e.getMessage());
return null;
}
}
private RSAPublicKey fetchPublicKeyFromJwks(String keyId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(jwksUrl))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Failed to fetch JWKS: " + response.statusCode());
}
// Parse JWKS response and extract public key for keyId
// This is a simplified example - real implementation would use JSON parsing
return parseJwksResponse(response.body(), keyId);
}
private RSAPublicKey parseJwksResponse(String jwksJson, String keyId) throws Exception {
// Simplified parsing - use a proper JSON library in production
// Extract the base64-encoded public key for the given keyId
// Convert to RSAPublicKey and return
// This is pseudo-code for demonstration
String publicKeyBase64 = extractPublicKeyFromJwks(jwksJson, keyId);
byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
private String extractPublicKeyFromJwks(String jwksJson, String keyId) {
// Implement JWKS parsing logic here
return ""; // Placeholder
}
@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
}Multi-Tenant Key Provider:
public class MultiTenantRSAKeyProvider implements RSAKeyProvider {
private final Map<String, TenantKeySet> tenantKeys;
private final String currentTenant;
private static class TenantKeySet {
final Map<String, RSAPublicKey> publicKeys;
final RSAPrivateKey privateKey;
final String privateKeyId;
TenantKeySet(Map<String, RSAPublicKey> publicKeys,
RSAPrivateKey privateKey,
String privateKeyId) {
this.publicKeys = new HashMap<>(publicKeys);
this.privateKey = privateKey;
this.privateKeyId = privateKeyId;
}
}
public MultiTenantRSAKeyProvider(String currentTenant) {
this.tenantKeys = new ConcurrentHashMap<>();
this.currentTenant = currentTenant;
}
public void addTenant(String tenantId, Map<String, RSAPublicKey> publicKeys,
RSAPrivateKey privateKey, String privateKeyId) {
tenantKeys.put(tenantId, new TenantKeySet(publicKeys, privateKey, privateKeyId));
}
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
TenantKeySet tenantKeySet = tenantKeys.get(currentTenant);
if (tenantKeySet == null) {
return null;
}
return tenantKeySet.publicKeys.get(keyId);
}
@Override
public RSAPrivateKey getPrivateKey() {
TenantKeySet tenantKeySet = tenantKeys.get(currentTenant);
return tenantKeySet != null ? tenantKeySet.privateKey : null;
}
@Override
public String getPrivateKeyId() {
TenantKeySet tenantKeySet = tenantKeys.get(currentTenant);
return tenantKeySet != null ? tenantKeySet.privateKeyId : null;
}
}Interface for parsing JWT header and payload JSON strings into structured objects.
/**
* Parser interface for JWT parts providing structured access to header and payload data
*/
public interface JWTPartsParser {
/**
* Parse a payload JSON string into a Payload object.
* @param json the JSON string representing the payload
* @return a Payload instance providing access to payload claims
* @throws com.auth0.jwt.exceptions.JWTDecodeException if JSON parsing fails
*/
Payload parsePayload(String json) throws JWTDecodeException;
/**
* Parse a header JSON string into a Header object.
* @param json the JSON string representing the header
* @return a Header instance providing access to header claims
* @throws com.auth0.jwt.exceptions.JWTDecodeException if JSON parsing fails
*/
Header parseHeader(String json) throws JWTDecodeException;
}Usage Examples:
import com.auth0.jwt.interfaces.JWTPartsParser;
import com.auth0.jwt.interfaces.Payload;
import com.auth0.jwt.interfaces.Header;
import com.auth0.jwt.impl.JWTParser;
// Use the default parser implementation
JWTPartsParser parser = new JWTParser();
// Parse header JSON
String headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"key-1\"}";
try {
Header header = parser.parseHeader(headerJson);
String algorithm = header.getAlgorithm(); // "HS256"
String keyId = header.getKeyId(); // "key-1"
System.out.println("Parsed algorithm: " + algorithm);
} catch (JWTDecodeException e) {
System.err.println("Failed to parse header: " + e.getMessage());
}
// Parse payload JSON
String payloadJson = "{\"sub\":\"user123\",\"iss\":\"auth0\",\"exp\":1234567890}";
try {
Payload payload = parser.parsePayload(payloadJson);
String subject = payload.getSubject(); // "user123"
String issuer = payload.getIssuer(); // "auth0"
System.out.println("Parsed subject: " + subject);
} catch (JWTDecodeException e) {
System.err.println("Failed to parse payload: " + e.getMessage());
}/**
* Generic key provider interface for dynamic key resolution
*/
public interface KeyProvider<U, R> {
U getPublicKeyById(String keyId);
R getPrivateKey();
String getPrivateKeyId();
}
/**
* RSA-specific key provider interface
*/
public interface RSAKeyProvider extends KeyProvider<RSAPublicKey, RSAPrivateKey> {
RSAPublicKey getPublicKeyById(String keyId);
RSAPrivateKey getPrivateKey();
String getPrivateKeyId();
}
/**
* ECDSA-specific key provider interface
*/
public interface ECDSAKeyProvider extends KeyProvider<ECPublicKey, ECPrivateKey> {
ECPublicKey getPublicKeyById(String keyId);
ECPrivateKey getPrivateKey();
String getPrivateKeyId();
}
/**
* JWT parts parser interface
*/
public interface JWTPartsParser {
Payload parsePayload(String json) throws JWTDecodeException;
Header parseHeader(String json) throws JWTDecodeException;
}Install with Tessl CLI
npx tessl i tessl/maven-com-auth0--java-jwt