Skip to content
ecdsa.com
Java · Kotlin

Nimbus JOSE + JWT

Connect2id's Nimbus JOSE + JWT, the JOSE library inside Spring Security and many Java OAuth servers: a processor whose key selector is the algorithm allow-list, unsigned tokens refused, and 60 seconds of default clock skew.
License
Apache-2.0
Checked version
10.10 ·
Source
bitbucket.org/connect2id/nimbus-jose-jwt
Documentation
connect2id.com/products/nimbus-jose-jwt

Compatibility at a glance

JOSE and JWT

JWS algorithms
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

Nimbus covers JWS, JWE, JWK and JWT with two layers: low-level objects (SignedJWT, ECDSASigner, MACVerifier) and the DefaultJWTProcessor, which ties key selection, signature checks and claims checks together. Spring Security's resource server is built on it, so its defaults are what most Spring APIs enforce.

With the processor, the JWSVerificationKeySelector is the allow-list: configured for ES256, it refused an RS256 token and an HS256 token keyed with the EC key's PEM text with Another algorithm expected, or no matching key(s) found, and without any selector it refuses everything. Unsigned tokens are rejected (Unsecured (plain) JWTs are rejected, extend class to handle). The low-level API has no such protection — a MACVerifier built from the PEM text verified that forged HS256 token — so code that picks a verifier from the token's header recreates the classic confusion bug. Use the processor, as the sample does.

DefaultJWTClaimsVerifier allows 60 seconds of clock skew by default, and exp is optional unless listed as a required claim — the sample requires sub and exp and pins the issuer. ES256 refuses a P-384 key (The ES256 algorithm is not allowed or supported by the JWS signer), and a DER-encoded signature simply fails to verify; check the signature length with the JWT debugger.

Version 10.10 round-tripped the HS, RS, PS and ES families plus EdDSA and the newer Ed25519 name. Ed25519 needs Google Tink on the classpath: without it, signing failed with NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair. ES256K needs a provider with secp256k1 such as Bouncy Castle, since the JDK has none.

To check what an identity provider publishes, use the JWKS checker; for encrypted tokens, the JWE inspector.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/nimbus-jose-jwt/run.sh ran Nimbus JOSE + JWT 10.10 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

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

    HS, RS, PS and ES at 256/384/512, EdDSA and Ed25519 signed and verified on the JDK; ES256K and Ed448 did not round-trip with the JDK's providers.

    Recorded by the check
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA, Ed25519
    evidence jws-algorithms: ES256K: not round-tripped with the JDK providers
    evidence jws-algorithms: Ed448: not round-tripped with the JDK providers
  • 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 token with a DER-encoded ES256 signature failed verification.

    Recorded by the check
    fact jws-der-signature = rejected
    evidence jws-der-signature: not verified
  • Algorithm allow-list

    required

    DefaultJWTProcessor needs a key selector: without one it fails with No JWS key selector is configured, and a selector for ES256 rejected an RS256 token.

    Recorded by the check
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: DefaultJWTProcessor with a JWSVerificationKeySelector for ES256, RS256 token: BadJOSEException: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
    evidence jwt-algorithm-allowlist: DefaultJWTProcessor without a key selector: BadJOSEException: Signed JWT rejected: No JWS key selector is configured
  • Unsigned tokens (alg none)

    rejected

    The processor rejects unsigned tokens with Unsecured (plain) JWTs are rejected, extend class to handle.

    Recorded by the check
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: DefaultJWTProcessor: BadJOSEException: Unsecured (plain) JWTs are rejected, extend class to handle
    evidence jwt-alg-none: SignedJWT.parse: ParseException: Invalid JWS header: Not a JWS header
  • Public key used as an HMAC secret

    rejected

    An HS256 token keyed with the PEM text was rejected by the processor configured for ES256, but a MACVerifier built from that PEM text by hand verified it.

    Recorded by the check
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: DefaultJWTProcessor with the ES256 key selector: BadJOSEException: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
    evidence jwt-hmac-public-key: a MACVerifier built from the PEM text by hand: verified
  • Curve checked against alg

    enforced

    Signing ES256 with a P-384 key fails with The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384].

    Recorded by the check
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: JOSEException: The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384]
  • Default clock skew

    60s

    DefaultJWTClaimsVerifier allows 60 seconds (DEFAULT_MAX_CLOCK_SKEW_SECONDS).

    Recorded by the check
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: DefaultJWTClaimsVerifier.DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60
  • Expiry claim required

    no

    A token without exp passed the default claims verifier; listing exp as required made it fail with JWT missing required claims: [exp].

    Recorded by the check
    fact jwt-exp-required = no
    evidence jwt-exp-required: with DefaultJWTClaimsVerifier requiring exp: BadJWTException: JWT missing required claims: [exp]
  • Ed25519 without Google Tink

    fails

    Without Google Tink on the classpath, signing with Ed25519Signer failed with NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair.

    Recorded by the check
    fact ed25519-without-tink = fails
    evidence ed25519-without-tink: NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair

Usage

Sign an ES256 token with a key id and process it with an ES256-only key selector and a claims verifier that requires sub, exp and the issuer.
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.*;
import com.nimbusds.jwt.proc.*;
import java.util.*;

public class Usage {
    public static void main(String[] args) throws Exception {
        ECKey key = new ECKeyGenerator(Curve.P_256).keyID("2026-10").generate();
        JWTClaimsSet claims = new JWTClaimsSet.Builder().subject("user-42").issuer("https://auth.example.com")
            .expirationTime(new Date(System.currentTimeMillis() + 600_000)).build();
        SignedJWT jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(key.getKeyID()).build(), claims);
        jwt.sign(new ECDSASigner(key));

        // The key selector is the algorithm allow-list; the claims verifier requires iss and exp.
        DefaultJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
        processor.setJWSKeySelector(new JWSVerificationKeySelector<>(JWSAlgorithm.ES256, new ImmutableJWKSet<>(new JWKSet(key.toPublicJWK()))));
        processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
            new JWTClaimsSet.Builder().issuer("https://auth.example.com").build(), new HashSet<>(List.of("sub", "exp"))));
        System.out.println("sub: " + processor.process(jwt.serialize(), null).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 -Dartifact=com.nimbusds:nimbus-jose-jwt:10.10 -DoutputDirectory=lib >/dev/null
Command
java -cp lib/nimbus-jose-jwt-10.10.jar Usage.java
Output must match (regular expression)
sub: user-42

Libraries