Quantcast
Channel: Blog – Stormpath User Identity API
Viewing all articles
Browse latest Browse all 278

JJWT - JSON Web Token for Java and Android

$
0
0

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.

Java JSON Web Tokens – Designed for Simplicity

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.

For example:

java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.MacProvider;
import java.security.Key;

// 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:

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

A SigningKeyresolver can inspect the JWS header and body (Claim or 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:

java
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:

java
Jwts.builder().setHeaderParam("kid", your_signing_key_id_NOT_THE_SECRET).build();

Enhanced JWT Security Options

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:

java
MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)
RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)
EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)

The generate methods that accept a SignatureAlgorithm argument know to generate a key of sufficient strength that reflects the specified algorithm strength.

How The JJWT Library Works

The JJWT library provides all the end-to-end functionality that the producer and consumer of the tokens require.

Token Behavior: Creation, Signing, Parsing and Verification

Because of the the builder-based fluent interface nature of JJWT, the creation of the JWT is basically a two-step process:

  1. The definition of the internal Claims of the token, like Issuer, Subject, Expiration, Id and its signing Key
  2. 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 key.

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

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

Internal JWT Structure: Header, Payload, Signature

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:

  1. The header will be Base64 URL Encoded,
  2. The payload will be Base64 URL Encoded,
  3. The encoded header and payload will be concatenated, appending a “.” in between them,
  4. A signature will be created for the resulting JWT string using the provided key,
  5. 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:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJKb2UifQ.WmxS1IZ-1iH1ZZ1dKBcpZGjU-IvTh88FUUMUR83J4oUuYYyBia-JjQebI0XBeVvNToRSC-_bzFM3nCQD-p2a6w

where:

  1. eyJhbGciOiJIUzUxMiJ9 is the encoded header,
  2. eyJzdWIiOiJKb2UifQ is the encoded payload, and
  3. WmxS1IZ-1iH1ZZ1dKBcpZGjU-IvTh88FUUMUR83J4oUuYYyBia-JjQebI0XBeVvNToRSC-_bzFM3nCQD-p2a6w is the generated signature

JWT Claims

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 (iss): getIssuer() and setIssuer(String)
  • Subject (sub): getSubject() and setSubject(String)
  • Audience (aud): getAudience() and setAudience(String)
  • Expiration (exp): getExpiration() and setExpiration(Date)
  • Not Before (nbf): getNotBefore() and setNotBefore(Date)
  • Issued At (iat): getIssuedAt() and setIssuedAt(Date)
  • JWT ID (jti): getId() and setId(String)

They are all available via the Jwts.claims() factory method.

Exceptions

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 RuntimeExceptions, with 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

JJWT is Open Source :)

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.


Viewing all articles
Browse latest Browse all 278

Trending Articles