Validate JWT tokens using JWKS in Java
One of the advantages of JWT tokens is that you can validate a token using a simple cryptographic operation. This is a really beautiful feature that is the result of how JWT is designed: JWT tokens are simply encoded and signed JSON.
Any client can check the signature. The client just needs access to the public/private part of the key set used for signing (depending on the algorithm). And here is where JWKS comes in handy.
JWK stands for JSON Web Key and it is a JSON data structure that represents a cryptographic key. The trailing S in JWKS means Set. JWKS is, as you can easily infer, a set of JSON Web Keys and it is a format used to share keys (usually public keys) between different services.
We can use JWKS to expose the public keys used by the signing party to all the clients that need to validate signatures. The anatomy of a JWKS is something like this:
As you can see, the format exposes all the information related to a given key. The most important fields for us are:
- n: the key itself.
- alg: the signing algorithm.
- kid: a unique id for every key in the set.
Using this information, a client can have access to the public keys needed to validate a signed token.
Let’s see how the process works with an example. The first thing we need is a source of JWT tokens.
The Oauth server
We typically get our JWT tokens from an OAuth server. In this example, we are going to use Hydra as our OAuth server. Hydra comes with docker-compose files that make it easy to start using it.
First off, we need to clone the project’s repo:
Once cloned, we can use the provided docker-compose files to start the OAuth server:
Now we can create the client we need for our example:
And in less than a couple of minutes, we can obtain a JWT token for our example:
Let’s take a look at our just created token:
That doesn’t look readable at first, but we can use https://jwt.io/ to decode the token and take a look at its contents:
That looks much better!
We can see that the token includes the classic JWT fields, including the algorithm used for the signature and the id of the key used for signing the token.
We can use that id to retrieve the correct key from the JWKS and validate the signature. But we need to know first where we can get the JWKS for the hydra server. The URL where the JWKS lives is .well-known/jwks.json
curl http://localhost:4444/.well-known/jwks.json | jq .
At this point, we have all the bits needed to validate the signature:
- The token carries a signature.
- The token provides information about the key used to sign it.
- The JWKS endpoint provides all the details about the signature.
We could write our own implementation to validate the key… but we don’t need to reinvent the wheel. It is much smarter to use a proven library to perform this task.
Auth0 libraries
Auth0 provides libraries for handling JWT tokens in different languages, including Java. We need a couple of libraries in order to perform the signature verification.
First off, we need a library for JWT handling:
This library provides JWT handling features: token decoding, access to the token claims, etc.
Additionally, we need a library for JWKS handling:
Verify JWT token signature
With these dependencies in place, we can easily verify our token’s signature:
Let’s break down the code above:
- In the first line of the snippet, we use auth0's JWT library to decode our token. The resulting
DecodedJWT
object allows us to access the content of our token: the claims, the signature, the algorithm used when the token was signed, the key id used to sign it, etc. - In the second line, we use the JWKS library to get a
JwkProvider
from the JWKS URL. We don’t need to provide the path where the JWKS is located, the library knows where the key set is located. The returnedprovider
object is a Java representation of a key set, providing a single method that allows us to retrieve a JWK by id:
- In line number three, we use the provider to get the key used when signing the token. The returned
jwk
object is, again, a Java representation of the information exposed by the JWKS endpoint. - Finally, we instantiate an RSA256 algorithm using the public key contained in the
jwk
object and we use this algorithm instance to verify our JWT token.
At this point, we can be (cryptographically) sure that the token was signed by our OAuth server (or any other component with access to the RSA256 private key that matches the public key provided by the JWKS endpoint).
This is enough to assume that our token is legit. But in order to be valid, a token must be both legit and not expired. Let’s add expiration verification to our snippet:
The error management here is rudimentary but the idea is simple:
- Verify signature.
- Check expiration.
- Use the token claims.
Wrapping up
JWT tokens are a great tool to share information between different systems. They are convenient to use and are, by design, easy to verify with simple cryptographic operations.
Using JWKS for public key sharing provides an extra layer of convenience to this verification process.