Framework for minting JWS objects and signed JWTs

Security protocols that mint tokens often require developers to perform key selection operations. OpenID Connect provider software, like the Connect2id server, mints ID tokens that are signed with a JWS algorithm configured for each relying party. This means the OpenID provider will need to fetch a key with the matching algorithm from its store. Providers also periodically rotate their keys, a highly-recommended practice, so the selected key must also be active and not expired.

To address those needs a mini framework for minting JWS objects (of which signed JWTs are a subset) was contributed to the Nimbus JOSE+JWT library and released in version 9.8.

Features of the minting framework:

  • Supports minting of generic JWS object as well as signed JWTs.

  • The signing key can be sourced from an in-memory JWK set, URL, file, database, etc.

  • Automatically selects a signing key that matches the JWS algorithm.

  • Selects the first matching key by default, to support key rotation scenarios (this behaviour can be overridden).

  • Automatically populates the JWS header with the following parameters from the selected key, if those parameters are present:

    • kid -- the key identifier
    • x5t#S256 -- the key SHA-256 thumbprint
    • x5t -- the key SHA-1 thumbprint (deprecated)
    • x5c -- the key X.509 certificate chain
    • x5u -- the key X.509 certificate chain URL
  • Optional SecurityContext for passing parameters to the key source.

  • Thread-safe.

The framework classes can be found in the com.nimbusds.jose.mint package.

Here is an example demonstrating the minting of a signed JWT from a JWK set:

import java.util.*;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
import com.nimbusds.jose.jwk.source.*;
import com.nimbusds.jose.mint.*;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.*;

// Generate a JWK set with two keys

// RSA key pair for the JWS algorithms RS256, RS384, RS512, PS256, PS384
// and PS512
JWK rsaJWK = new RSAKeyGenerator(2048)
    .keyIDFromThumbprint(true)
    .keyUse(KeyUse.SIGNATURE)
    .generate();

// EC key pair with a P-256 curve for the JWS algorithm ES256
JWK ecJWK = new ECKeyGenerator(Curve.P_256)
    .keyIDFromThumbprint(true)
    .keyUse(KeyUse.SIGNATURE)
    .generate();

JWKSet jwkSet = new JWKSet(Arrays.asList(rsaJWK, ecJWK));

// The minter needs to be provisioned with a JWK source. The
// interface allows for any kind of backend, e.g. a database or
// flat file. Here the source is an in-memory JWK set.
JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(jwkSet);

// The minter is thread-safe and reusable
ConfigurableJWSMinter<SecurityContext> minter = new DefaultJWSMinter<>();
minter.setJWKSource(jwkSource);

// To mint a signed JWT pass the desired JWS header and claims set
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
    .type(JOSEObjectType.JWT)
    .build();

JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
    .subject("alice")
    .issuer("https://c2id.com")
    .issueTime(new Date())
    .build();

// The minter will select a JWK that matches the JWS header
// algorithm, e.g. an RSA signing key for the RS256 algorithm. If
// multiple matching keys are found the first will be used for the
// signing. If the JWK has parameters kid (key ID), x5u (X.509 certificate
// URL), x5c (X.509 certificate chain) or x5t#256 (X.509 certificate
// thumbprint) they will be copied into the JWS header, to identify the
// signing key to the JWT consumer.
JWSObject jwsObject = minter.mint(header, claimsSet.toPayload(), null);

// Serialise the JWT
System.out.println(jwsObject.serialize());

Example decoded JWT header, the kid was copied from the RSA JWK:

{
  "alg" : "RS256",
  "typ" : "JWT",
  "kid" : "imTcQBlwyEP8RNryfO_ez52LWic8zZvqIXfH8lUMqYY"
}

The claims set:

{
  "iss" : "https://c2id.com",
  "sub" : "alice",
  "iat" : 1617812700
}