1. Introduction

Nowadays, we have more and more concerns about performance and and at the same time how systems can communicate fast and reliably. Many times we want to send information which need to abide by NDA (Non-Disclosure Agreement), follow GDPR (Global Data Protection Regulation) and essentially stay confidential and safe as much as possible. Sensitive data sometimes must also move through the web publicly and trigger actions at the other end of the wire. Sometimes we want to generate actions that will cause data mutations. In these cases we not only looking at protecting our data. Furthermore, we want to make sure that the actions triggered by sending our data are trusted. We can protect our data in several ways. Most commonly, we send the data via a TLS (Transport Layer Security). Not only that, but we can then Encrypt our data, and we can also use Certificates to create trust relationships between two parties.

In this article, I want to discuss the JWT standard and further see how can we integrate JWT in a common Enterprise application. In this case we will have a look at KumuluzEE.

Let’s have a look at some basic concepts. 🤓

JWT or JSON Web Token, or better yet, JavaScript Object Notation Web Token, is a standard defined in RFC7519. This standard has been, like all RFC (Request For Comments) standards, defined, written and published by the IETF (Internet Engineering Task Force). It can be defined in multiple ways. Generally we can say that JWT is a compact, URL safe form of transmitting claims between two parties. One way of simplifying what a claim is, is basically to describe it as a name/value pair which contains information. We need this information to guarantee a few important aspects of our internet communication. We need to make sure that the information we receive is validated and trusted. This is basically it. In other words we just want to trasmit JSON messages accross the wire.

In order to implement this standard, we can use several frameworks that can help us implement a Java enterprise application. Spring Boot is being used widely. Many times it’s also being wrapped under another name in propriety software from certain organizations like banks and other financial organizations. For my example I decided to do something different. Instead of Spring Boot, we are going to have a look an example with KumuluzEE. The point is to identify exactly what JWT is and what it looks like. Java Enterprise Application are basically applications that can be deployed in an application server or just run on its own via the use of an embedded server. As an example, Spring Boot applications run on an embedded Tomcat server. In this article our focus will be set to KumuluzEE. Just like Spring Boot it also contains an embedded server. Except that in this case it is called Jetty. This is used in combination with Weld in order to provide CDI(Context Dependency Injection). All Java EE and Jakarta EE technology standards are compatible with this framework.

2. Knowledge Expectations

To follow this article properly, we are expected to understand Java Enterprise architectures and frameworks. It is nice if we understand Spring Boot, KumuluzEE, JakartaEE, Payara Enterprise, JBoss, Wildfly, Glassfish, Weblogic, or any other enterprise application/application server which can serve any java application. We need to understand or have a minimum idea of what CDI is. It is also advised to read about S.O.L.I.D. principles before continuing. I will be showing some bash scripts. It is also great if you can read them. If you are still tripping through the entrails of good practices and architectures… You know what? It doesn’t matter! 👍I think you will still get a pretty good idea at the end of this article. Let’s keep our heads up and dive into this! 🙌

2. Example

In order to exemplify how JWT works in its basic form, I had to think of a way to present it. Classic examples where security is a concern are banks. However, making a whole bank application to show how JWT works would be a waste of time and maybe too many concepts would be involved. Instead, what I made is a very, ultra, über simple banking system. In this banking system we aren’t really concerned about the data itself. Our main concern is to show how data flows through the wire and how users get access to certain areas of our application. I’m also not going to discuss TLS or how we can send encrypted information through the wire. We will keep our focus in JWT in its purest form.

Our case is a banking system located in a GYM. In my example I am using all characters from the Dua Lipa — Let’s Get Physical Work Out Video from Dua Lipa. This is just a fun way to show how JWT works.

3. Architecture

Before we start, let’s just sketch our running application. It’s a very simple application but it’s still a good thing to draw it:

Basic Request and Response

The reason why this is so simple is that JWT is really simple. JWT can be integrated with OAuth2, Okta SSO or any other authorization mechanism. In this case what we are doing is establishing authentication. In our application, we are going to use JWT and with it, authenticate our message using a signature. We won’t log in though. Instead we will authorize users to use our application after a successful authentication. At this point it’s easy to see that JWT at its core is actually a very small part of a full application. Nonetheless, some functionality needs to be added. These are the Resources we need:

Balance system

Credit system

Let’s just say that our basic system will only register money in and credit requests. Essentially it will just accumulate values. Let’s also assume that some people will be able to get credit and others won’t. Some people will be able to store money 💵 and other people will be able to get credit 💸.

4. Choosing technologies

As mentioned in the introduction, we will use KumuluzEE as our enterprise application framework, and we will implement an ultra basic application in a way that we can look at basic JWT terminology and concepts.

In order to start, the mandatory technologies are Java 11. We will need maven, git, a Java-compatible IDE like Intellij and a shell of some sort.

Let’s do this!🎬

5. Setup

In order to begin our application we need quite a few KumuluzEE dependencies. This is mainly because KumuluzEE, just like Spring Boot needs a couple of dependencies. Let’s have look at the pom file briefly:

pom.xml for the Gym Banking App

Let’s discuss a few dependencies briefly. As you read this, please follow our pom.xml file from top to bottom. This is important in order to understand the following explanation. In order to be able to configure our app with all the JWT parameters we need, we have to add a MicroProfile library to it. At the same time we need a JSON processing library. This will what Johnson Core does. We need of course the core of KumuluzEE to work. Jetty is the underlying server that runs the KumuluzEE framework. This is why we need it in our dependencies. Considering we need CDI, we also need a library that supports it. In order to enable our REST endpoints we need the rest library of KumuluzEE. In order to get our API we then need a Geronimo library. This will ensure that we have an implementation of JSR-374 available. We also need to interpret our JWT and its JSON formatted contents. Lombok is not really needed. It just makes everything beautiful and shiny! Logback is also important to have so that we can better interpret logs and understand our results.

Let’s now look at our resources folder.

To start off let’s first understand what do we expect to find in this folder. We need to configure our application with something related to JWT, Logback and finally we need to say something about the beans we are going to create.

Let’s look at the simplest file there. The beans.xml can be found in META-INF:

This is just a typical and as you may be thinking now, a bit of an old file. At this point the idea is just to get KumuluzEE running. We do have an exclude action. This tells Weld not to consider class Accounts in its scanning for beans action. This is important because with the implementation we are using, Weld will basically consider every class with an empty constructor as a bean. We will see later on why we don’t want Accounts to be considered a bean. For the moment let’s keep in mind that we are making requests under the Request scope. This is logical because every request can have a different user. Let’s move on.😊

Let’s now see how logback is implemented. It is also found in META-INF:

logback.xml

This is just a very straightforward configuration for our logs.

Finally, maybe the most important file of our application. This is the config-template. At this point it is important to note that some of the files I’ve created in this project are part of a template structure. I will explain more in that later. this template file is supposed to be turned in to a config.yml file which will be read by MicroProfile. This file is located in the root of resources:

config.yml

We will see later on what exactly all of these properties actually mean. All of them are self-explanatory. The publicKey and issuer are all parameters which will be replaced. We will explore that later on. Our bash script will make sure they get replaced.

We are almost ready to go coding, but first, let’s have a look at the our JWT token structure. ✋

6. Hands-on code

Let’s make our very small application. This section will explain how can we get our application to work with JWT. What we want to see is if we can specify users to access some of our REST methods and not others.

One of the ways to start looking at this code is to first have a look at our plain JWT token. Here is our admin example:

jwt-token-admin.json

Each one of these names in our json is referred to as claims. In our example we see a few Reserved claims:

iss — This is the issuer of the token. We can arbitrarily choose a value for this. We will be using jwtenizr.jar to create tokens. It defaults to “airhacks”. That’s fine, but we can change that if we want. The value of this parameter must match the issuer variable to be replaced in the config.yml we have seen before.

— This is the issuer of the token. We can arbitrarily choose a value for this. We will be using jwtenizr.jar to create tokens. It defaults to “airhacks”. That’s fine, but we can change that if we want. The value of this parameter must match the variable to be replaced in the we have seen before. jti — This is a unique identifier of the token. We can for example use this claim to prevent a token to be used twice or more times.

— This is a unique identifier of the token. We can for example use this claim to prevent a token to be used twice or more times. sub — This is the subject of the token. It can be the user or anything we like. It’s important to keep in mind that this can also be used as an identifier, key, naming or anything we want.

— This is the subject of the token. It can be the user or anything we like. It’s important to keep in mind that this can also be used as an identifier, key, naming or anything we want. upn — User principal name. This is used to identify the principal the user is using.

— User principal name. This is used to identify the principal the user is using. groups — This is an array of the groups the current user belongs to. Essentially this will determine what a request with this token can do.

In our token we then see a few Custom claims. We can use this just as well as the Reserved claims

user_id — We will use this to set the user id.

— We will use this to set the user id. access — We will determine the access level of the user.

— We will determine the access level of the user. name — The name of the user.

Aright! Having this in place, it’s time to start coding!! 💻. Let’s do this!🏎

7. Hands on code

Let’s make recap what we know so far. We know we will communicate with tokens with a structure we have determined. Further we have set up the configuration of our application, the logback configuration and finally we set up a custom configuration for the enterprise bean lookup.

Let’s look at package model. Here we will find 3 classes. These classes basically just represent an aggregation of accounts and the representation between client and account. This way we can start by looking at class Client.java:

Client.java

This first model class is the representation of our client. Our client for our case only has a name. Easy peasy. That’s what we want the moment. 🚀.

Now it’s time to look at our Account.java:

Account.java

In this class we basically set up an accountNumber, a client, a currentValue and finally a creditValue. Notice that we are defaulting all values to 0. We are also using BigDecimal, purely because we are dealing with money. Money must be exact and cannot suffer system round-ups or round downs. This means in other words and as an example that a number such as 0.0000000000000000000000000000000000000000000000000001 euros must remain that number all the time. Also we want to add values to our account. This is where method addCurrentValue comes to exist. For the same reasons we will also top up our credit with the addCreditValue.

Finally, in the last piece of our data setup we come accross class Accounts:

Accounts.java

This is essentially just an aggregator of all our accounts. We will use its map content to mimic the behavior of a database.

Now let’s look at the controller package. This is where we create our application running with our data model. First let’s have a look at class BankApplication:

BankApplication.java

With this, we are saying 3 important things. With the LoginConfig annotation we define it to use and understand JWT tokens. The ApplicationPath defines what the application root is. Finally the DeclareRoles defines the roles that are going to be used and accepted by our application. Roles and Groups are interchangeable terms in this situation.

As an extra top this application, let’s create an annotation to identify account-related beans:

AccountsProduct.java

Next, we create the aggregation object factory:

AccountsFactory.java

This factory is the reason why we disabled lookup specifically for Accounts.java. Instead of allowing the lookup process to create a bean, we create the aggregator instance ourselves. Using the Produces annotation, allows us to create the bean. Using our custom annotation, AccountsProduct, we make the use of this bean more specific. Finally by using ApplicationScoped, we define its scope as being the Application scope. In other words, the account aggregation bean will behave as a singleton object across the application.

What we now need is two Resources. This is basically the same as Controllers. A different name, but exactly the same usage.

Let’s look at the AccountsResource.java class:

AccountsResource.java

Take a moment to look at this class. The Path annotation defines how to reach this resource from the root. Remember that we are using ‘/’ as root. In this case accounts is our root access point for this resource. All of our recourses, in our case only two, are running with scope RequestResource. With annotation Produces determine that all responses to all requests, regardless of their type will take the form of JSON formatted messages.

To inject our aggregator we just use the combination of the Inject annotation and AccountsProduct annotation:

@Inject

@AccountsProduct

private Accounts accounts;

This matches what we defined in the factory.

Further we are also injecting two principles:

@Inject

private Principal principal;



@Inject

private JsonWebToken jsonWebToken;

Both JsonWebToken and Principal will be the same and we will see that in our logs.

In our resources, we can always inject claims from a request with a certain token:

@Inject

@Claim("user_id")

private JsonNumber administratorId;



@Inject

@Claim("access")

private JsonString administratorLevel;

This is accomplished with the combination of the Inject and Claim annotations. The name place under the Claim annotation defines which claim we want to inject. We have to be careful with the type we define our parameters with. In our example we only need JsonString and JsonNumber types.

First, let’s look at how we are creating accounts and users:

@POST

@RolesAllowed({ "admin", "client", "credit"})

public Response createAccount() throws JsonProcessingException {

final Account currentAccount = ofNullable(accounts.getAccountMap()

.get(name.getString())).orElse(Account.builder()

.client(Client.builder()

.name(name.getString())

.build())

.build());



return createResponse(currentAccount);

}

@POST

@RolesAllowed({ "admin", "user"})

@Path("user")

public Response createUser() throws JsonProcessingException {

final Account currentAccount = ofNullable(accounts.getAccountMap()

.get(name.getString())).orElse(Account.builder()

.client(Client.builder()

.name(name.getString())

.build())

.build());



return createResponse(currentAccount);

}

The purpose here is to be able to separate methods and give them different permissions. In our example, they both just create an account, but it’s important to notice that only users with role user can use the createUser method. In the same way, only users with roles client and credit can access method createAccount.

Let’s now look in detail at the PUT request method of this resource:

@PUT

@Path("{value}")

@RolesAllowed({ "admin", "client" })

public Response cashIn(

@PathParam("value")

Long value) throws JsonProcessingException {

final Account userAccount = accounts.getAccountMap()

.get(name.getString());

if (isNull(userAccount)) {

return Response.serverError()

.build();

}

userAccount.addCurrentValue(value);

return createResponse(userAccount);

}

We know that annotation PUT indicates that this method is only accessible with requests of type PUT. Annotation Path then tells Jetty that the path to this method is a value. This is also known as a PathParam. Finally we can define this method be only allowed to be used by users with roles admin or client. The input value is then passed on to our Long value variable by the usage of the PathParam.

If we don’t define any roles, then any user with the right token will be able to access these methods.

The CreditResource.java is implemented in the same way:

CreditResource.java

The only difference is that instead of using roles admin and client we are now using admin and credit roles. Also notice that accounts for users will never be created in this resource. That’s only possible via the account's resource.

Done! 🎸

Now that we know how the code is implemented let’s first recap which methods have we made available in our REST service. 🚀

8. Application Usage

Let’s check the list of the services being used:

All Endpoints

9. Generating test environment

I have created a bash file in the root folder. This file is called setupCertificates.sh. Let’s have a look into it to have an idea of what it does:

Environment generation

Please follow the file as I explain what it does. This is important so that we understand exactly what it is doing:

We download the jwtenizr.jar. This is runnable jar which allow for a quick creation of tokens. It creates the private and public keys in jwtenizr-config.json. It creates the default issuer airhacks. The issuer can be changed later. Finally it creates a token. We will see how to read this token later on.This token contains 3 extra Header claims. These are kid, typ and alg. It follows the following format:

{

"kid": "jwt.key",

"typ": "JWT",

"alg": "RS256"

}

Let’s look at these claims more closely

kid — works as a hint claim. It indicates which sort of algorithm we are using.

works as a hint claim. It indicates which sort of algorithm we are using. typ — it is used to declare IANA media types. There are three options JWT (JSON Web token), JWE (JSON Web Encryption) and JWA (JSON Web Algorithms). These types aren’t relevant for our experiment. We will only see that our token isn’t really well encrypted and that it’s really easy to decrypt it. We will also see that although we can decrypt tokens, we cannot that easily tamper the to perform other actions.

it is used to declare IANA media types. There are three options (JSON Web token), (JSON Web Encryption) and (JSON Web Algorithms). These types aren’t relevant for our experiment. We will only see that our token isn’t really well encrypted and that it’s really easy to decrypt it. We will also see that although we can decrypt tokens, we cannot that easily tamper the to perform other actions. alg — This is how we define the signature type we want to use. Signing can be considered as a cryptographic operation that will ensure that the original token has not been changed and it trusted. In our case we are using RS256 otherwise known as RSA Signature with SHA-256.

Once we have our public key, we can finally use it to change our template. The new config.yml file should look something like this:

config.yml

The second step is to create four files. For every single plain tokens in jwt-plain-tokens, we will create four commands. The first command is to create users that can effectively do things with their accounts. These are users with profiles admin, client, and credit. Let’s run file createAccount.sh, in order to create them. The second command will create the rest of the users which don’t possess any rights yet. This is file createUser.sh. Let’s run it. Now we’ll see that all users are finally created. Let’s now look into details about transactions and look at the remaining two commands.

One to cashin and another to ask for more credit. The first generated file is the sendMoney.sh bash script. Here we can find all requests to cashin:

sendMoney.sh

The same users have also their credit requests assigned to them:

askCredit.sh

As we can see, our gym is composed of all 13 characters of the Dua Lipa — Let’s Get Physical Work Out Video. Some of our characters can do everything, others cannot do anything in and others can only cashin or just ask for credit. Also notice that I am obfuscating sensitive information. These tokens normally should not be shared or be visible in on particular URL. They are yes always available via the browser developer console, but anyways is to protect some requests being made.

In both methods, when we make a deposit or when we ask for credit, notice that for each request, we are sending a random number between 1 to 500. This important because we are about to make these requests.

We are now almost ready to start our application, but first, let’s take a dive into a bit more theory. 📚

10. The entrails of a JWT token

Now that we have generated our tokens, let’s look into one of the. I am going to show you an obfuscated token and we are going to use that to understand this.

Here is our token:

FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN

What’s important here to notice is that our token is divided into three parts.

Header — This is a Base64 encoded JSON configuration header, just as we discussed above.

— This is a Base64 encoded configuration header, just as we discussed above. Payload — This is a Base64 encoded JSON payload. This is where we defined our Reserved and Custom claims. We can also define here Private and Public claims. Both of them fall under Custom claims. As a quick note, we can do what we want with both of these claims. However public claims are referred to as the ones defined in the IANA JSON Web Token Registry. It important that we name our tokens in a certain way in order to avoid collisions with the registry. Public claims can also be defined as standard optional. Private claims follow no standard and it’s up to us to define them.

— This is a Base64 encoded payload. This is where we defined our and claims. We can also define here and claims. Both of them fall under claims. As a quick note, we can do what we want with both of these claims. However public claims are referred to as the ones defined in the IANA JSON Web Token Registry. It important that we name our tokens in a certain way in order to avoid collisions with the registry. claims can also be defined as standard optional. claims follow no standard and it’s up to us to define them. Signature — This is where we can be a bit creative. The signature is a cyphered combination of the Header and the Payload. We decide the algorithm we want to use and this bit of the token will basically determine if the message we are sending is to be trusted. It is unique to that combination and our server will use the public-key we created to determine if we have a match. If you remember from the above we are using RS256 in our example.

Before we continue, please note that both the Header and the Payload can be decyphered in our example. We just “can’t” tamper with the payload or the header and still make it trusted. The protection against the potential effects of a malicious token, can only be protected by the algorithm we choose. So choose wisely:

Choosing the Holy Grail — Indiana Jones and The Last Crusade, 1989

If you are working in an organization where top secret information is a concern, such as a bank, please DON’T do what we are about to do. This is only a way for us to check online the content of the tokens we’ve generated locally.

Let’s begin.

First let’s got to https://jwt.io/ and fill out our JWT token. Use the token you’ve just generated.

Using https://jwt.io/ to check the contents of our token

Let’s examine what we have here. This is our administrator token. Of that person is Dua Lipa in our example. We can see that our parameters are all available. In our list we see sub, aud, upn, access, user_id, iss, name, groups and finally the jti. We also have some extra claims. Let’s look at them:

auth_time — This when the authentication has happened. Our token as been authenticated on the Friday, 20 March 2020 07:54:56 GMT+01:00.

iat — This when the token has been created. In our case this happens simultaneously as the auth_time.

exp — This is the expiry date of the token. It expires on Friday, 20 March 2020 08:11:36 GMT+01:00. We didn’t specify any expiry date in our token. This means that JWT uses its default value of ~15 minutes.

Alright! We’ve done with theory and hopefully we are all still here. 🤓📚🚀

It’s time for some testing!

11. Running the application

The code is ready to be used in GitHub. If we check the code out and open it with Intellij we need to be aware the we can’t run this application like a Spring Boot application. There is not psvm trick to make it run. Instead we can run the generated jar directly and make sure that we make a mvn build just before. Here is how I am using it at the moment:

Environment setup to run application

Let’s now run the setupCertificates.sh script again. I don’t know how much time you took to get here but it’s very likely that the 15 minutes are already gone at this point. Just in case, just run them again.

Let’s start our app!

We can start it the old fashion way:

$ mvn clean install

$ java -jar your-financeje-banking/target/your-financeje-banking-1.0-SNAPSHOT.jar

Or we can just run it through our ready to go running configuration

Let’s first run sendMoney.sh. Now let’s run askCredit.sh.

Go to your browser go to this location:

We should be getting this result:

Complete JSON result after one run of both askCredit.sh and sendMoney.sh scripts

As we can see, we are missing Dee, Ginger Snap and Sunny. This is because they have only been given the user group. Notice also that Upset has been given no credit. Upset is our only client which doesn’t have the group credit.

If we look at the logs, we can see that successful operations take this format:

---

Sending money to admin

HTTP/1.1 200 OK

Date: Fri, 20 Mar 2020 07:22:57 GMT

X-Powered-By: KumuluzEE/3.7.0

Content-Type: application/json

Content-Length: 34

Server: Jetty(9.4.15.v20190215) {"balance":71,"client":"Dua Lipa"}

---

A 200 lets us know that the operation went successfully.

In the case of Dee, Ginger Snap and Sunny, both operations fail and then we will get this kind of message:

---

Sending money to dee

HTTP/1.1 403 Forbidden

Date: Fri, 20 Mar 2020 07:22:58 GMT

X-Powered-By: KumuluzEE/3.7.0

Content-Length: 0

Server: Jetty(9.4.15.v20190215) ---

A 403 let’s us know that our JWT token has been validated and it’s trusted. However the user is forbidden, in other words, it has no access to the designated method.

Let’s tamper our tokens shall we? Let’s change some of the tokens of the sendMoney.sh file. We should be getting this:

---

Sending money to admin

HTTP/1.1 401 Unauthorized

Date: Fri, 20 Mar 2020 07:37:37 GMT

X-Powered-By: KumuluzEE/3.7.0

WWW-Authenticate: Bearer realm="MP-JWT"

Content-Length: 0

Server: Jetty(9.4.15.v20190215) ---

This 401 means that our token was not validated. It means that the public key which the server uses to check if our token is to be trusted, has found no match.

11. Conclusion

We have reached the end of our session. Thank you for following this.

As we could see how JWT tokens are compact and very much less verbose than their XML counterpart, the SAML tokens. We have seen how easy it is to create and use tokens to get certain authorizations needed for certain methods and how do we get there via a signed token.

There are quite a lot of advantages of using this. Of course this application is very simple and in reality, it’s the server that will generate the tokens for us. We don’t do this normally in a real use case. We can only do this because locally we also have the public key which we use to create our signature. In a real environment, users should never be able to access the public key used to sign tokens. This sort of leakage would be tremendously dangerous for a production environment.

I do want to make another article with real application running without having to generate tokens locally. I find however very important to get our “hands dirty” and really get the feel over how JWT works. Hopefully with this, I have given you a good introduction and a very light one about how JWT tokens work.

I have placed all the source code of this application in GitHub

I hope that you have enjoyed this article as much as I enjoyed writing it.

I’d love to hear your thoughts on it, so please leave your comments below.

Thanks in advance for your help and thank you for reading!

12. References