Occassionally here at Stormpath, we find time for open-source projects in the authentication and user security space. One such project, which is taking off in the Java community, is JJWT – a self-contained Java library providing end-to-end JSON Web Tokens creation and verification.
JJWT aims to be the easiest library for creating and verifying JSON Web Tokens (JWTs) on the JVM, and started as a side-project of our CTO, Les Hazlewood.
The JSON Web Token for Java and Android library is very simple to use thanks to its builder-based fluent interface, which hides most of its internal complexity. This is great for relying on IDE auto-completion to write code quickly.
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
Key key = MacProvider.generateKey();
String jwtString = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS512, key).compact();
That’s all… in just a single line of code you now have a JSON Web Token containing the
Subject Joe signed with
key so its authenticity can later be verified.
Now let’s verify the JWT:
assert Jwts.parser().setSigningKey(key).parseClaimsJws(jwtString).getBody().getSubject().equals("Joe"); //Will throw `SignatureException` if signature validation fails.
To determine which key was used to sign the token, JJWT provides a handy little feature that will allow you to parse the token even if you don’t know which key was used to sign the token.
SigningKeyresolver can inspect the JWS header and body (
String) before the JWS signature is verified. By inspecting the data, you can find the key and return it, and the parser will use the returned key to validate the signature. For example:
SigningKeyResolver resolver = new MySigningKeyResolver();
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(resolver).parseClaimsJws(compact);
The signature is still validated, and the JWT instance will still not be returned if the jwt string is invalid, as expected. You just get to ‘inspect’ the JWT data for key discovery before the parser validates it.
This of course requires that you put some sort of information in the JWS when you create it so that your
SigningKeyResolver implementation can look at it later to look up the key. The standard way to do this is to use the JWS
kid (‘key id’) field, for example:
When it comes to creating, parsing and verifying digitally signed compact JWTs (aka JWSs), all the standard JWS algorithms are supported out of the box:
- HS256: HMAC using SHA-256
- HS384: HMAC using SHA-384
- HS512: HMAC using SHA-512
- RS256: RSASSA-PKCS-v1_5 using SHA-256
- RS384: RSASSA-PKCS-v1_5 using SHA-384
- RS512: RSASSA-PKCS-v1_5 using SHA-512
- PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
- PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
- PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
- ES256: ECDSA using P-256 and SHA-256
- ES384: ECDSA using P-384 and SHA-384
- ES512: ECDSA using P-512 and SHA-512
No need to install an additional encryption library; all these algorithms are provided by JJWT. It even provides convenient key generation mechanisms, so you don’t have to worry about generating safe/secure keys:
MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)
RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)
EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)
generate methods that accept a
SignatureAlgorithm argument know to generate a key of sufficient strength that reflects the specified algorithm strength.
The JJWT library provides all the end-to-end functionality that the producer and consumer of the tokens require.
Because of the the builder-based fluent interface nature of JJWT, the creation of the JWT is basically a two-step process:
- The definition of the internal Claims of the token, like
Idand its signing
- The actual compaction of the JWT in a URL-safe string according to the JWT Compact Serialization rules.
The final JWT will be a Base64 URL encoded string signed with the specified Signature Algorithm using the provided
After this point, the token is ready to be shared with the other party. When received, they can parse the contained info fairly easily:
Jwt jwt = Jwts.parser().setSigningKey(key).parse(compactJwt);
This method returns an expanded (not compact/serialized) JSON Web Token. Internally it will do its best to determine if is a JWT or JWS, or if the body/payload is Claims or a String. It might be difficult for the internal algorithm to automatically identify the kind of token. In that case, you can use the
parse(String, JwtHandler) method which allows for a type-safe callback approach that may help reduce code or
During parsing time, the JWT is first verified with the provided
key. The signature algorithm is identified via the
alg property located in the header section of the JWT. The specified algorithm will be used to veriy the token with the provided
key. If the verification fails, the
parse method will not continue and will throw a
When the token is being created, the JJWT library stores all the properties in a
Map structure. During compaction, the following steps will be carried out in this order:
- The header will be Base64 URL Encoded,
- The payload will be Base64 URL Encoded,
- The encoded header and payload will be concatenated, appending a “.” in between them,
- A signature will be created for the resulting JWT string using the provided
- Finally, the signature will be concatenated to the JWT string appending a “.” in between them.
As a result, all the provided information will finally look like this:
eyJhbGciOiJIUzUxMiJ9is the encoded header,
eyJzdWIiOiJKb2UifQis the encoded payload, and
WmxS1IZ-1iH1ZZ1dKBcpZGjU-IvTh88FUUMUR83J4oUuYYyBia-JjQebI0XBeVvNToRSC-_bzFM3nCQD-p2a6wis the generated signature
As already mentioned, all the defined JWT values are ultimately stored in a JSON
Map. JWT standard names are provided as type-safe getters and setters for convenience. They are:
- Issuer (
- Subject (
- Audience (
- Expiration (
- Not Before (
- Issued At (
- JWT ID (
They are all available via the
Jwts.claims() factory method.
JJWT carries out different kind of validations while working with the JWT. Upon errors, it will throw different kind of
Exceptions so the developer can handle them accordingly. All JJWT-related exceptions are specifically
JwtException as the base class.
These errors cause specific exceptions to be thrown:
ClaimJwtException: thrown after a validation of a JTW claim failed
ExpiredJwtException: indicating that a JWT was accepted after it expired and must be rejected
MalformedJwtException: thrown when a JWT was not correctly constructed and should be rejected
PrematureJwtException: indicates that a JWT was accepted before it is allowed to be accessed and must be rejected
SignatureException: indicates that either calculating a signature or verifying an existing signature of a JWT failed
UnsupportedJwtException: thrown when receiving a JWT in a particular format/configuration that does not match the format expected by the application. For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application requires a cryptographically signed Claims JWS instead
Hopefully, we have shown how JJWT is extremely simple to use and understand. If you need to create and verify JSON Web Tokens (JWTs) on the JVM, this is the right tool to use.
Furthermore, like many libraries Stormpath supports, JJWT is completely free and open source (Apache License, Version 2.0), so everyone can see what it does and how it does it. Do not hesitate to report any issues, suggest improvements and even submit some code!
Let us know what you think in the comments below.