Code Review Videos > Java > Generate & Verify JWTs with JJWT

Generate & Verify JWTs with JJWT

In the past few posts, we’ve been exploring Spring Security with the long-term objective of setting up a user management system that mirrors common money-making setups often used in SaaS applications.

The goal is to configure Spring Boot / Spring Security to handle a hierarchy of roles:

  1. Super Admin: This is the God level role with maximum permissions across our system.
  2. Team Administrators: These would be our ‘paying customers’. Ideally a team can have >=1 team admin, but I haven’t got anywhere close to figuring that out just yet.
  3. Team Members: Every team has one or more team members. Team admins should also be considered team members.

This structure is one I’ve found commonly requested on larger / real world applications, and so seems like a fitting goal to work towards. Every team consists of at least one member while maintaining clear role-based access control (RBAC) principles. There are further challenges to this sort of thing, such as what happens if the team leader leaves the team? It does get messy, but that’s far down the road from here.

So far, we’ve explored user roles and scenarios where multiple users can have multiple roles. However, the authentication mechanism we’ve used so far has been HTTP Basic. While this works for learning purposes, in a real-world application, we would typically require something more robust and secure.

The next logical step in my mind is to use JSON Web Tokens (JWTs).

JWTs are a popular choice for authentication in modern applications due to their flexibility and stateless nature. They are very popular where applications are split into a “frontend” and “backend” setup.

We’ll cover more about what a JWT is, and stateful vs stateless authentication below.

However, what I’ve found—and why there’s been a bit of a gap since my last post—is that implementing JWT authentication in a Spring Boot and Spring Security application can be particularly challenging for beginners.

To ease into this more complex topic, this post will focus on a smaller, manageable subset: generating and verifying JWTs in isolation. By breaking this down, hopefully this part can be understood in isolation before moving on to the more challenging aspect of integrating with Spring Boot & Spring Security.

Before we dive in any further, there are three topics to cover as prerequisites. These are:

  1. Stateful vs Stateless authentication
  2. What are JSON Web Tokens (JWTs)?
  3. Should we even use JSON Web Tokens?

Stateful vs Stateless Authentication

There are two approaches to handling user authentication: stateful and stateless authentication. Each has its own strengths and trade-offs, making them suitable for different types of applications.

Stateful authentication relies on the server maintaining session data about each authenticated user. This session information is stored on the server, often in Redis in the real world. The client holds a session identifier (most commonly stored in a cookie) that the server uses to look up the session.

Stateless authentication is the opposite of Stateful authentication.

Stateless authentication removes the need for the server to store any session information, and therefore run yet another service (e.g. Redis).

Instead, all relevant data about the user’s authentication state is baked in to their token. The server then only needs to validate this token on each request, ensuring it hasn’t expired, and that it hasn’t been tampered with.

This approach also has the added benefit of making things easier to scale because you don’t need to worry about that shared session store.

So, what is a JSON Web Token (JWT)?

A JSON Web Token – far more commonly referred to as a JWT, pronounced as a Jot – consists of three distinct parts.

There are:

  1. Header
  2. Payload
  3. Signature

You can easily identify these parts in a token because they are separated by dots (.).

Here’s an example of a JWT:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzb21lX3VzZXJuYW1lX2hlcmUifQ.q-08kBryT5wL_N8sOaYDhJSUHRR-OWg4lPjM7YfGsKczKeLBY7733MaBo4ixUE_6VhiYGILjG7hqRvJ7vup59gCode language: CSS (css)

It’s pretty hard to read this in my opinion, but there’s a really useful website called jwt.io that makes this process much more visual:

Now the three different sections are colour coded, and – perhaps surprisingly – you can see the decoded payload data right there on screen. More on why that is below.

Let’s briefly look at the three different parts that make up any JWT.

Header

The first part of our JWT is the header. This contains metadata about the token.

The minimum amount of data to include in the header is the alg or algorithm that was used to sign the token. In the JJWT library we will be using, there are plenty to choose from:

jjwts sig algos

You may also see the typ (or type) of token that this is – in the screenshot above I have explicitly set the typ to JWT.

You can also add in any other key / value pairs that you want or need. I’ve added in an example header, just to show this is possible.

Payload

The payload can include anything that your application needs to know about the user or session. Some common examples include:

  • Username: Pretty obvious, and useful for front end applications.
  • User roles: We’ve covered these previously. When working with JWTs, these are often referred to as claims.
  • Token Expiry: A timestamp indicating when the token will expire. This is one of the biggest gotchas with JWTs, so we will cover this more below.
  • Anything else: Any additional information that is useful to your application.

The payload is just a collection of key-value pairs, much like any standard JSON object.

The really important part – and a bit of a head scratcher to people first meddling with JWTs – is why is the payload is not encrypted?

The payload is intended to be read.

By anyone.

You are not supposed to store anything confidential in a JWT. Therefore by default, the JWT payload should not need encryption.

Like the rest of the JWT, it is encoded using base64 to make it easier to send using HTTP. No need to worry about remembering to encode special characters and what not.

Encrypting and decrypting tokens on every request would be computationally expensive, far more so than verifying the signature.

The signature (coming next) ensures the token hasn’t been tampered with.

If you do need to encrypt the payload, the JJWT library we will be using can do this. But at that point we would be dealing with JSON Web Encryption (JWE), and that’s yet another increase in complexity over JWTs.

Signature

Finally, the signature is the part of the token that ensures the integrity and authenticity of your data.

It allows a recipient (our server side application) to verify that:

  1. The token hasn’t been tampered with.
  2. It was issued by a trusted source.

The signature is created by combining:

  • The encoded header.
  • The encoded payload.
  • A secret key or private key.

This combination is then hashed together using the signing algorithm specified in the header. We saw this above where we set our alg to HS512.

The resulting hash forms the signature.

When a recipient validates the token, it recreates the signature using the header, payload, and secret key. If the recreated signature matches the one in the token, the data can be considered authentic.

Getting the signature for the purposes of manual testing is a bit fiddly, but we will cover that below also.

Should You Use JSON Web Tokens?

When I’ve created SaaS applications in the past, I’ve used JWTs, and they’ve worked absolutely fine. In fact, JWTs are widely used in many paid applications and enterprise systems. You’ll find them implemented across various platforms because they offer flexibility and scalability in stateless authentication.

However, I came across a viewpoint—probably over a year ago now—that was critical of building your own authentication process. I can’t remember exactly where I saw it, but the gist of the argument was:

“Don’t even bother using JSON Web Tokens. In fact, don’t bother building your own authentication system at all.”

It’s not exactly what he said, but Jordan says something similar

Instead, the suggestion was to offload the entire authentication process to a third-party provider using OAuth. The idea is to delegate the responsibility of keeping your users’ credentials secure to companies like Google, Microsoft, GitHub, or other trusted identity providers. This approach reduces your workload and transfers the burden of security to organisations with far greater resources to ensure its implementation is safe.

While this argument has its pros and cons, particularly for small teams or projects without dedicated security expertise, there are still valid cases for using JWTs.

Regardless, for learning purposes I think it’s really useful to know about JWTs as they are so prevalent in the real world.

Let’s Get Coding

For our JWT implementation, we’re going to use the Java JWT (JJWT) library. This library is widely recommended, which is why I chose it. Here are a few reasons it stood out:

  • Popularity: It has over 10,000 stars on GitHub, need I say more? Well… ok then, some more.
  • Great Documentation: The library is well-documented, with numerous examples and tests to help you get started.
  • Ease of Use: It offers straightforward APIs for generating, signing, and verifying tokens.

What We’ll Do

We’ll implement two key features as part of this example:

  1. Token generation
  2. Token verification and parsing (reading)

Because we’re using the JJWT library, all of the complexity is abstracted away from us.

All we need to do is use the various methods the library supplies and we’re able to create our token with the headers and payload values we desire.

We will then output this to the terminal, and immediately ‘use’ it to test out the parsing, validation, and extraction functionality.

Dependencies

We will need to include the JJWT dependency in our pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>uk.co.a6software</groupId>
    <artifactId>jwt-generate-and-verify-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

</project>Code language: HTML, XML (xml)

There are no other dependencies for this one, and the dependency list above is taken directly from the JJWT installation steps on their GitHub repo.

First Pass

I’m going to put all the code in one file, as that is easier for the purposes of this tutorial write up. However, as you will see from the code over on GitHub, in reality I have split this code into two different files, more reflective of the real world.

Here we go:

import io.jsonwebtoken.Jwts;

import javax.crypto.SecretKey;

class JwtUtil {
    private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();

    public static String generateToken() {
        return Jwts.builder()
                .subject("your_username")
                .signWith(SECRET_KEY)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(JwtUtil.generateToken());
    }
}Code language: Java (java)

This is the absolute bare minimum code required to create a realistic looking JWT.

You can play around with this.

Try removing the subject. You should find this gives an invalid payload warning.

And what if you comment out the code that signs the JWT?

Well, that generates an unusual looking but seemingly valid JWT with an invalid signature:

eyJhbGciOiJub25lIn0.eyJzdWIiOiJ5b3VyX3VzZXJuYW1lIn0.Code language: CSS (css)
jwt with no signature

However, if you go with the code I have entered above, and run the program, you should see a JWT printed out to your console.

Paste that into jwt.io and your decoded data should be similar to the screenshot below:

basic jjwt jwt example

Changing The Signing Algorithm

As covered earlier in this post, there are a variety of options for signing your JWTs.

Here’s the screenshot again for reference:

jwt signing options

I would encourage you to play around with these.

The ones you will find easiest to work with are the MacAlgorithm‘s. These are hash-based message authentication (HMAC) codes.

Use one of these lines:

private static final SecretKey SECRET_KEY = Jwts.SIG.HS256.key().build();
private static final SecretKey SECRET_KEY = Jwts.SIG.HS384.key().build();
private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();Code language: PHP (php)

Whichever of these lines you use, the outcome is that whenever the JwtUtil class is first loaded into memory, it will be evaluated and run only once, storing the value in memory for the lifetime of the application.

This second point, about the secret key being stored in memory for the lifetime of the application is really important.

What that says is that when our code stops running, the secret key is lost. Unless we kept a record of whatever it was, any JWTs signed by that secret key will now all be considered invalid.

This sort of thing is fine for the purposes of small demos, but back in the real world we need to keep this key around so it can survive application restarts. Now, this is getting outside the scope of what we’re doing here as you will need to secure this key somehow. I’m not yet sure how Spring Boot and Spring Security address this problem, or whether they offload it on to the developer to solve. So for now, I’m not able to offer any potential solutions unfortunately.

You can try the other algorithms by following the examples on the readme:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.SignatureAlgorithm;

import java.security.KeyPair;

class JwtUtil {
    private static final SignatureAlgorithm alg = Jwts.SIG.ES512; //or ES256 or ES384
    private static final KeyPair pair = alg.keyPair().build();

    public static String generateToken() {
        return Jwts.builder()
                .subject("your_username")
                .signWith(pair.getPrivate(), alg)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(JwtUtil.generateToken());
    }
}Code language: PHP (php)

Or:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Curve;
import io.jsonwebtoken.security.Jwks;

import java.security.KeyPair;

class JwtUtil {
    private static final Curve curve = Jwks.CRV.Ed25519; //or Ed448
    private static final KeyPair pair = curve.keyPair().build();

    public static String generateToken() {
        return Jwts.builder()
                .subject("your_username")
                .signWith(pair.getPrivate(), Jwts.SIG.EdDSA)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(JwtUtil.generateToken());
    }
}Code language: JavaScript (javascript)

Manually Validating The JWT

A problem we have currently is that we do not have any way to manually verify the JWT.

In order to do this, we need to know the secret key’s value.

However, we can’t simply print out the key:

import io.jsonwebtoken.Jwts;

import javax.crypto.SecretKey;

class JwtUtil {
    private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();

    public static String generateToken() {
        System.out.println(SECRET_KEY.toString());

        return Jwts.builder()
                .subject("your_username")
                .signWith(SECRET_KEY)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(JwtUtil.generateToken());
    }
}
Code language: Java (java)

Whilst this program compiles and runs, the output is … not what we want:

javax.crypto.spec.SecretKeySpec@fa77edd9Code language: CSS (css)

Fortunately – and I do mean very fortunately – whilst probing around the docs to figure out a solution to the issue above, about saving the key somewhere so I could re-use it, I found this line:

If we print out this code, this gives us a Base64 encoded representation of our secret key which we can then use to verify our JWT’s via jwt.io:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Encoders;

import javax.crypto.SecretKey;

class JwtUtil {
    private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();

    public static String generateToken() {
        System.out.println(Encoders.BASE64.encode(SECRET_KEY.getEncoded()));

        return Jwts.builder()
                .subject("your_username")
                .signWith(SECRET_KEY)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(JwtUtil.generateToken());
    }
}
Code language: Java (java)

Which results in something like this when we run the program:

execution result with base64 secret key

The first line is our base64 encoded secret key, and the second line is our JWT.

We can then plug these values in to jwt.io:

verified jwt at jwt.io

Nice.

This manually validates that our code is behaving.

Adding More Data To Your JWT

Next comes the fun part.

As covered above, a JWT consists of the header, payload, and signature.

We’ve spent some time looking at how to sign the JWTs. Now let’s do the more interesting thing (imho) which is to add stuff to our JWT.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Encoders;

import javax.crypto.SecretKey;
import java.util.*;

class JwtUtil {
    private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();

    public static String generateToken(String username) {
        System.out.println(Encoders.BASE64.encode(SECRET_KEY.getEncoded()));

        List<String> roles = Arrays.asList("USER", "TEAM_OWNER", "ADMIN");
        Map<String, Object> customClaims = new HashMap<>();
        customClaims.put("roles", roles);
        customClaims.put("email", "chris@codereviewvideos.com");

        return Jwts.builder()
                .header()
                .type("JWT")
                .add("Example header from", "Code Review Videos")
                .and()
                .subject(username)
                .claims(customClaims)
                .signWith(SECRET_KEY)
                .compact();
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter your username: ");
        String username = scanner.nextLine();

        System.out.println(JwtUtil.generateToken(username));

        scanner.close();
    }
}Code language: Java (java)

I’m going to make the assumption that if you’re confident enough to be playing around with JWTs, that you’re passed the point of needing explanations on List, Map, and Arrays.

I’ve added a touch of dynamisicm to proceedings by taking the username from the user at runtime. This is for novelty value only, no need to do that, static values are fine.

With the values:

Enter your username: 
billy bob
sZh4hJKfoshBCYVBM1e/VWiJfSqKzAQwmnohSUtlO5n/HwfGGCofNliw9t2VxjgzWbKE4pL3KH6naSxa6SdQFA==
eyJ0eXAiOiJKV1QiLCJFeGFtcGxlIGhlYWRlciBmcm9tIjoiQ29kZSBSZXZpZXcgVmlkZW9zIiwiYWxnIjoiSFM1MTIifQ.eyJzdWIiOiJiaWxseSBib2IiLCJyb2xlcyI6WyJVU0VSIiwiVEVBTV9PV05FUiIsIkFETUlOIl0sImVtYWlsIjoiY2hyaXNAY29kZXJldmlld3ZpZGVvcy5jb20ifQ.khb17pPj5v_mLt5ibDD1CcSieBZvtbvxUVnzTMT-ifEUQjSQDB2K30tlMFuejs3eD0W2DpJ7FYvRgidi5nXXig

I see the output:

billy bob jwt

Parsing Your JWT Server Side

Lastly we will need a way for our system to receive and process our previously provided JWTs.

In the real world, a user would have successully authenticated, received their JWT, and was now trying to ‘do something‘. That might be accessing a resource on our API, posting in some new data, or things like that.

In the case of this example, we will keep the existing setup where we dump out the JWT to the console, and then provide a new line to read that JWT back in, and print out the contents of the JWT:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Encoders;

import javax.crypto.SecretKey;
import java.util.*;

class JwtUtil {
    private static final SecretKey SECRET_KEY = Jwts.SIG.HS512.key().build();

    public static String generateToken(String username) {
        System.out.println(Encoders.BASE64.encode(SECRET_KEY.getEncoded()));

        List<String> roles = Arrays.asList("USER", "TEAM_OWNER", "ADMIN");
        Map<String, Object> customClaims = new HashMap<>();
        customClaims.put("roles", roles);
        customClaims.put("email", "chris@codereviewvideos.com");

        return Jwts.builder()
                .header()
                .type("JWT")
                .add("Example header from", "Code Review Videos")
                .and()
                .subject(username)
                .claims(customClaims)
                .signWith(SECRET_KEY)
                .compact();
    }

    public static Claims extractClaims(String jwt) {
        return Jwts.parser()
                .verifyWith(SECRET_KEY)
                .build()
                .parseSignedClaims(jwt)
                .getPayload();
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter your username: ");
        String username = scanner.nextLine();

        System.out.println(JwtUtil.generateToken(username));

        System.out.println("Paste back in your JWT:");
        System.out.println(JwtUtil.extractClaims(scanner.nextLine()));

        scanner.close();
    }
}
Code language: Java (java)

Which should now give something like this:

Enter your username: 
chris
uqTHPhNnXbwO4j4kTPxKA4ftObSTq8LS6t/XYum6mtT...
eyJ0eXAiOiJKV1QiLCJFeGFtcGxlIGhlYWRlciBmcm9tIjoi....
Paste back in your JWT:
eyJ0eXAiOiJKV1QiLCJFeGFtcGxlIGhlYWRlciBmcm9tIjoi....
{sub=chris, roles=[USER, TEAM_OWNER, ADMIN], email=chris@codereviewvideos.com}

I’ve truncated lines there for readability.

But really that is it. The basics of creating and consuming JWTs.

Now that we know this, we are more suitably prepared for integrating this code with Spring Security. And that’s coming up next.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.