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.
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.