Creating Signed JWTs using Nimbus JOSE + JWT

Scott Brady
Scott Brady
Kotlin

I’ve been using the Java library “Nimbus JOSE + JWT” to create JWTs recently. It has been pretty useful for playing around with uncommon JOSE algorithms such as ES256K and EdDSA. Considering that these are not supported out-of-the-box in .NET yet, being able to use another stack to generate test data has been invaluable.

So, this is one of those blog posts where I write down how to use the library for signature generation and validation for future Scott to reference once he inevitably forgets.

Installation

To install Nimbus JOSE + JWT, you’ll need the nimbus-jose-jwt package from Maven. Here’s a snippet from my build.gradle:

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "com.nimbusds:nimbus-jose-jwt:7.8.1"
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

JWT Signed using ECDSA

In this example, I’m going to create an EC key suitable for ES256K (curve secp256k1 with SHA-256), sign a JWT using my new private key, and verify the JWT signature with the corresponding public key.

If you’re looking for how to do this in .NET Core (using ES256), check out my previous article “JWT Signing using ECDSA in .NET Core”.

Creating an EC Key

To generate a private key for our curve, I’m going to use ECKeyGenerator. I’ll also supply a key ID as if we were going to share the JWT and share the public key as a JWK.

val key: ECKey = ECKeyGenerator(Curve.P_256K)
    .keyID("123")
    .generate()

Nimbus JOSE + JWT uses the Java Cryptography Architecture (JCA). So, if you need or want to use Bouncy Castle instead, you can do so via the JCA.

Signing a JWT

To generate a JWT, I need to create a header and a payload, use them to generate a SignedJWT, sign it using my private key, and then serialize the JWT into a URL-safe string.

val header = JWSHeader.Builder(JWSAlgorithm.ES256K)
    .type(JOSEObjectType.JWT)
    .keyID(key.keyID)
    .build();
val payload = JWTClaimsSet.Builder()
    .issuer("me")
    .audience("you")
    .subject("bob")
    .expirationTime(Date.from(Instant.now().plusSeconds(120)))
    .build()

val signedJWT = SignedJWT(header, payload)
signedJWT.sign(ECDSASigner(key.toECPrivateKey()))
val jwt: String = signedJWT.serialize()

Example ES256K JWT

eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU3NTEyODg5NX0.ha2bS5LynJ9nb7HxElJtzZ9hK4Z9LwvHcPcZHFLdpf1fbApSthqvngXU1M2y5XvdcTTFv4I9ts60UWkhaDuuXA

Verifying a JWT Signature

In this example, I am only going to validate the JWT’s signature. I’ll do this using the verify method on SignedJWT.

val isValid: Boolean = SignedJWT.parse(jwt)
    .verify(ECDSAVerifier(key.toECPublicKey()))

JWTs Signed using EdDSA

Using EdDSA in Nimbus JOSE + JWT is much the same as the above, but we’ll need to use a different curve (Ed25519), generate our key as an OctetKeyPair, and install Google’s Tink package:

implementation "com.google.crypto.tink:tink:1.2.2"

My implementation ended up looking like this:

// generate key
val key: OctetKeyPair = OctetKeyPairGenerator(Curve.Ed25519)
    .keyID("123")
    .generate()

// generate signed JWT
val header = JWSHeader.Builder(JWSAlgorithm.EdDSA)
    .type(JOSEObjectType.JWT)
    .keyID(key.keyID)
    .build();
val payload = JWTClaimsSet.Builder()
    .issuer("me")
    .audience("you")
    .subject("bob")
    .expirationTime(Date.from(Instant.now().plusSeconds(120)))
    .build()

val signedJWT = SignedJWT(header, payload)
signedJWT.sign(Ed25519Signer(key))
val jwt: String = signedJWT.serialize()

// validate signature (and only signature)
val isValid: Boolean = SignedJWT.parse(jwt)
    .verify(Ed25519Verifier(key.toPublicJWK()))

Example JWT Signed Using EdDSA

eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU3NTEyOTEyNH0.vTRyDo4R6cmeI1cfnUznyZBabQAb_IQOCrg4aq4--G-tJACNgvRn4a9gUIB4fShVlz5-LMtHvpV6ZP_faeLMAA

Source Code

You can find code samples for both ES256K and EdDSA on GitHub. You’re welcome future Scott.