Skip to content
ecdsa.com
Java · Kotlin

java-jwt (Auth0)

Auth0's java-jwt: a compact JWT library where each verifier is bound to one Algorithm object, JWT.decode() checks nothing, and the algorithm set stops at HS, RS and ES — no PS256 and no EdDSA.
License
MIT
Checked version
4.6.2 ·
Source
github.com/auth0/java-jwt
Documentation
github.com/auth0/java-jwt#readme

Compatibility at a glance

JOSE and JWT

JWS algorithms
HS256HS384HS512RS256RS384RS512ES256ES384ES512

java-jwt builds tokens with JWT.create()…sign(algorithm) and verifies them with JWT.require(algorithm)…build().verify(token). The Algorithm object carries both the algorithm and the key, so a verifier only ever accepts that one algorithm: an RS256 token, an unsigned token and an HS256 token forged with the EC key's PEM text all failed against an ES256 verifier with AlgorithmMismatchException.

The protection holds only while the Algorithm is chosen from configuration. JWT.require(Algorithm.none()) accepts unsigned tokens, and Algorithm.HMAC256(pemBytes) verified the forged HS256 token — code that picks the Algorithm from the token's alg header, or loads keys as raw bytes, recreates the confusion bugs the API otherwise prevents. JWT.decode() parses without checking the signature or any claim; it returned a token that had expired an hour earlier.

The algorithm set is HMAC256/384/512, RSA256/384/512 and ECDSA256/384/512 — no PS256, no ES256K and no EdDSA — so tokens from issuers that moved to RSA-PSS or Ed25519 need Nimbus or JJWT. ES256 refuses a P-384 key at signing time.

ES256 signatures are the 64-byte raw r‖s, and a DER signature fails with the generic The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA; the JWT debugger shows the signature length. There is no default leeway and exp is optional: the sample adds withClaimPresence("exp") and acceptLeeway(30) along with the issuer check.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/java-jwt/run.sh ran java-jwt (Auth0) 4.6.2 in the container image maven:3.9.16-eclipse-temurin-25 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • JWS algorithms

    HS256HS384HS512RS256RS384RS512ES256ES384ES512

    HS, RS and ES at 256/384/512 signed and verified; the Algorithm factory has no PS*, ES256K or EdDSA.

    Recorded by the check
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512
    evidence jws-algorithms: Algorithm factory methods: HMAC256/384/512, RSA256/384/512, ECDSA256/384/512, none; no PS*, ES256K or EdDSA
  • ES256 signature encoding

    raw

    ES256 signatures are 64 bytes of raw r‖s.

    Recorded by the check
    fact jws-ecdsa-encoding = raw
  • DER signature inside a JWS

    rejected

    A DER-encoded ES256 signature fails with SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA.

    Recorded by the check
    fact jws-der-signature = rejected
    evidence jws-der-signature: SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA
  • Algorithm allow-list

    required

    A verifier is bound to one Algorithm: an RS256 token against JWT.require(ECDSA256) failed with AlgorithmMismatchException.

    Recorded by the check
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: JWT.require(ECDSA256) on an RS256 token: AlgorithmMismatchException: The provided Algorithm doesn't match the one defined in the JWT's Header.
  • Unsigned tokens (alg none)

    opt-in

    An ES256 verifier rejects unsigned tokens; JWT.require(Algorithm.none()) accepts them.

    Recorded by the check
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: JWT.require(ECDSA256): AlgorithmMismatchException: The provided Algorithm doesn't match the one defined in the JWT's Header.
    evidence jwt-alg-none: JWT.require(Algorithm.none()): verified
  • Public key used as an HMAC secret

    rejected

    An HS256 token keyed with the PEM text failed against the ES256 verifier, but Algorithm.HMAC256(pemBytes) verified it.

    Recorded by the check
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: JWT.require(ECDSA256): AlgorithmMismatchException: The provided Algorithm doesn't match the one defined in the JWT's Header.
    evidence jwt-hmac-public-key: JWT.require(Algorithm.HMAC256(PEM bytes)): verified
  • Curve checked against alg

    enforced

    Algorithm.ECDSA256 with a P-384 key fails to sign with SignatureGenerationException.

    Recorded by the check
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: SignatureGenerationException: The Token's Signature couldn't be generated when signing using the Algorithm: SHA256withECDSA
  • Default clock skew

    0s

    No leeway by default: a token expired three seconds earlier was rejected.

    Recorded by the check
    fact jwt-clock-skew = 0s
  • Expiry claim required

    no

    A token without exp verified; withClaimPresence("exp") requires it.

    Recorded by the check
    fact jwt-exp-required = no

Usage

Sign an ES256 token and verify it with the issuer pinned, exp required and 30 seconds of leeway.
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.time.Instant;

public class Usage {
    public static void main(String[] args) throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        generator.initialize(new ECGenParameterSpec("secp256r1"));
        KeyPair pair = generator.generateKeyPair();
        Algorithm es256 = Algorithm.ECDSA256((ECPublicKey) pair.getPublic(), (ECPrivateKey) pair.getPrivate());

        String token = JWT.create().withSubject("user-42").withIssuer("https://auth.example.com")
            .withExpiresAt(Instant.now().plusSeconds(600)).sign(es256);

        // JWT.require() verifies; JWT.decode() only parses. Require exp and allow some leeway.
        DecodedJWT verified = JWT.require(es256).withIssuer("https://auth.example.com")
            .withClaimPresence("exp").acceptLeeway(30).build().verify(token);
        System.out.println("sub: " + verified.getSubject());
    }
}

The snippet harness runs every sample verbatim in the container image maven:3.9.16-eclipse-temurin-25, and the run fails unless the output matches.

Setup
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Command
java -cp 'lib/*' Usage.java
Output must match (regular expression)
sub: user-42

Libraries