JWT With Spring Boot 5
In this story, we will go through the process of configuring JWT with a Spring Boot application and I will be using a sample project to demonstrate how to configure JWT and use it as an authorization mechanism.
I would advice the readers to, please refer the diagrams at the bottom of the story while reading through the sections and visit the GitHub repository related to this story for better understanding of what is going on.
Buckle up, it’s a long way down!
What is JWT?
Before we ask what is JWT, let’s ask ourselves, what is a token in a server-client communication environment?
Simply put, it’s a way to prove yourself to another party, securely, that you are who you claim to be, in a stateless communication environment between a client and a server.
This token should include data to identify a user uniquely and what the user can do in that environment. We call this tokenizing data.
So, what is JWT?
There are few methods to tokenizing user authorities and JWT is one of them.
It’s a way of encoding your authorities (encoded by the server) as a token so that the token can be used to prove yourself to the server whenever you request resources.
I’m not going to explain more than that since this story is for another purpose. You can learn more about JWT from the official site.
Why do we need JWT?
Let’s say we are using username and password to authenticate in our application. Now, when you request something from the backend server via a HTTP request, if the resource needs proper authorization to access, first you need to prove that you have the proper authorization and you do that by sending your username and password. Now, imagine that you need to access resources from this server again and again. In this situation, you need to prove yourself to the server by sending your username and password in every attempt to access a resource.
Now, of course, you don’t have to do this if the server keeps current state of the authentication. But, if you need a stateless environment, you better find another way of authorizing users instead of asking them for username and password in each and every request.
That’s where token based authorization comes in. When you sign in to a system by providing your username and password, the system generates a token for you with the necessary data inside it. This token is sent to you as the response of your authentication request. For any request to access resources from the server, you can send this token instead of your username and password.
That’s why we need a token based authorization model and JWT happens to be a method of tokenizing your information that the server needs to validate your authorities to access resources (among other information).
Another approach to prove yourself to a server is by using cookies. Let’s briefly look at the difference between JWT and Cookies.
Cookies vs. JWT
JWT is self contained, meaning it includes data that the server needs to validate user authorities. Cookies are not self contained, it is just a reference to the user (a session id), meaning the server needs to fetch authorities of requested user by using the cookie, in order to validate authorities. Therefore, server also needs to keep every active sessions in cookie approach.
Also, if you have a micro-service architecture, this session pool cannot be in every server and client will have to keep multiple cookies for each service. So, JWT seems a good fit in such environments since the same JWT can be used in multiple servers as long as they have the same decoding algorithms and keys.
In the process of sending these with every request, if it’s cookies, browser will send cookies automatically and if it’s the JWT approach, we have to explicitly add the token to request header.
From the security aspect, cookies are open to CSRF attacks while in JWT approach, it’s not possible (doesn’t mean JWT are attack proof). In cookies also, we can prevent these attacks using different methods but we need to do them explicitly.
How to setup JWT?
In this section, we’ll go through the process of configuring JWT in our project. The content includes only necessary code snippets for each step. To understand the code properly you may have to see the full implementation and you can find it in the GitHub repository.
Big Picture
Before dive into any code, let’s see what we are going to build in a diagram and get the big picture into our heads.
If you don’t get what these relations are or have no clue what these classes or methods are, don’t worry. Let’s build these one by one while understanding the inner logic of each of them. But please, do refer this time to time while reading so that you know how they all connects and finally help us achieve our goal.
Adding JWT Libraries
To setup JWT, first we need the JWT libraries in our project, and we have maven to help us. We’ll ask maven to get us JWT libraries as our project dependancies by putting them in pom.xml as follows.
Adding these dependencies allow us access to JWT API within our application.
Role of JWT in Authentication
After adding JWT dependancies into our project, now it’s time for us to use JWT. As we discussed earlier, authentication happens with username and password by comparing them to the persisted users in the system. It has nothing to do with JWT. But, where JWT comes into play is when we has authenticated a user and ready to send the response to that auth request.
I will start from the login controller method, something that we all familiar with and gradually dive into configurations on the way. But, to make things (that we’ll find later) easier to explain, let’s just create the class that we will keep the customized configurations in. Normally, I’d name the class SecurityConfiguration.Java and put it in a package named configuration or config. But, that’s really up to you to decide.
@EnableWebSecurity annotation lets us override the default behavior of Spring Web Security through bean methods. Spring Documentation.
Authentication controller
Following is our login endpoint controller. We’ll breakdown the method implementation task by task but to summarize; first, it authenticates the user with given username and password and if the user is authenticated, then creates the JWT via TokenProvider (we’ll get to this later) and set it to the response with “Bearer ” concatenated in front.
Now, let’s look at the breakdown of authentication process.
Authentication service
First things first. Let’s check if the username and password came with the request are correct. For that, we need to extend Spring’s UserDetailsService class in our AuthenticationService class and override the loadUserByUsername() method to give Spring the user by the username came in the request (fetched from a data source).
If no user is found by the username, throw UsernameNotFoundException.
If a user is found by the username, we return the user as a type of UserDetail class provided by Spring (in this case, we return our own User type since that implements the UserDetails class as we’ll see later). Following is the implementation of method loadUserByUsername().
Normally, you would get the user from a database. Here, I have created a user with hardcoded values just for demonstration.
Did you notice that we didn’t check if the given password matches the password of the user that we found by the given username? That’s because comparing passwords is done by the Spring security and we just give the strategy (a method of encoding the password) on how to compare.
Since we have overridden the default configurations of Spring Security, Spring expects us to give the password encoder. Let’s add the preferred password encoder bean in the SecurityConfiguration class. In this case, I’m going with BCryptPasswordEncoder.
Token provider
If a user is found by the given username and, the password also matches, we get the username and authorities (any other information if necessary) of the user and we tokenize them using the createToken() method of TokenProvider that we implement as follows. Even though we named it as the token provider, we can consider this as the JWT token service of our project.
Here, authorities (as a String type) that we have fetched from the Authentication object are set to the token as claims and, the username as subject.
User and authorities
To get the necessary authorities from Authentication object, we need to implement UserDetails class provided by Spring Security in our User class (that we returned in loadUserByUsername() method) and override the method getAuthorities() to return the necessary user authorities.
Here’s how the user role enum is implemented with 3 basic roles SUPER ADMIN, ADMIN and USER.
Now, every time the authenticated client make a request to the server, client can send the token in request header to show that they are already signed in and has proper authorization to access whatever resources they are trying to access.
Open authentication endpoint
To make all above functionality work, the endpoint we are using for authentication (“/login”) must be accessible by any user without having to login first (obviously). We configure this from the security filter chain and we put it in our SecurityConfiguration class.
Now, we can implement the filterChain() method bean as follows (please refer the Figure 2 at the bottom to understand where these authorizations will be validated in Spring filter chain).
Here, we have opened “/login” route to the public (there can be other validations like origin of the request, etc.), authorized “/api/**” routes (where we have our business logic, if you checked out the GitHub repository) and closed all other routes to non-authenticated users.
Using JWT means our server can work as stateless, means no user session needs to be kept in the server. But, in the default filterChain() implementation, it is stateful. So we have told Spring that we want it to be stateless by adding that SessionCreationPolicy.STATELESS in session management configuration.
In this approach, we can also get rid of CSRF (Cross-Site Request Forgery) by telling spring to disable CSRF as we have done in the filterChain(). Because, with the way we authenticate a user, a CSRF attack isn’t possible (well, read the linked article above).
Wonder what jwtSecurityConfigurerAdapter() does? Let’s hold that for a moment and move on.
We still need to do some configurations in this filterChain() like configuring the http header by defining the content security policy and setting CORS filters. They are included in the sample project repository so, I’m not going to show them here. But, there will be some JWT related configurations in the following content.
Role of JWT in Authorization
Now that we have given client the JWT through login response and we have told Spring what endpoints are open to the public and what endpoints are closed or protected and also informed Spring that we are going with stateless approach in our application and the CSRF should be disabled;
What’s next?
It’s time to filter every request and if it includes JWT, validate the token and check whether the request has necessary permission to access the requested resources, means we are authorizing the requests.
Customized request filter implementation
We will achieve this by intercepting the request in the middle of the Spring security filter chain and injecting our own filter implementation as follows. To do that, we need to extend the class GenericFilterBean of Spring web filter and override the doFilter() method (refer the Figure 1 at the top and Figure 2 at the bottom to understand where this filter will run in Spring filter chain).
Let’s implement this doFilter() method to validate the JWT as follows. Inject the dependency TokenProvider through the JWTFilter constructor to use the methods we implemented.
Alright, let’s break it down.
We get the bearer token from headers by key “Authorization” and extract the token by removing “Bearer “ prefix if a token exists.
If a token doesn’t exist, we treat it as a login request since our application doesn’t allow any other endpoint to be accessed with non-authenticated requests.
Validate the token
If the token exists, to validate the extracted token we use validateToken() method that we are going to implement inside TokenProvider class as follows.
We use parseClaimsJws() of JWT Parser that was created with the base 64 secret inside TokenProvider constructor to check the validity of the token.
Extract user information from the token
If the token is valid, we get the containing details of the token as Authentication object and set it to Spring SecurityContextHolder (in doFilter() method) so that we can use the auth details whenever we need. Following is how we extract token details and set it to Authentication object from getAuthentication() method in TokenProvider class.
Now that we have implemented our own filter, we need to tell Spring where to put it inside the filter chain, because the filter chain contains some other filter interceptors including the UsernamePasswordAuthenticationFilter. So we need to be precise of where our filter should run.
Plug the customized filter implementation in filter chain
We do this by adding yet another configuration to our filterChain() in SecurityConfiguration class that we implemented earlier (Said I’ll come back to jwtSecurityConfigurerAdapter() later and, here we are).
Let’s put the jwtSecurityConfigurerAdapter() method implementation above the filterChain() method.
Intercept the request filter chain
Implementation of JWTConfigurer class is as follows.
As it shows, we tell Spring that we need our JWTFilter to run before the UsernamePasswordAuthenticationFilter. This way, UsernamePasswordAuthenticationFilter can skip the authentication process if the user is already logged in because if the JWT is valid, our filter set the authentication details to Spring SecurityContextHolder.
To understand where our JWTFilter and SecurityConfiguration filterChain() method is in the Spring filter chain, refer the following diagram. The diagram is designed to give a rough idea of how the filter chain works and does not follow any standards.
Refer the diagram in Figure 1 at the top to understand how our classes and methods work together to make use of JWT in our application.
End of Story
Remember, this is just normal authentication with username and password. We are using JWT just to let the client use it to let the service provider (ex: backend) know that client is already authenticated and the authorities client has. So that client doesn’t need to send username and password every time they request something and server doesn’t have to keep the authenticated session with user details for each and every user that has logged into the system.
As best practices, use constants where necessary and put information like secrets and policies in application.properties file for easy configuration between different environments like dev and prod (application.properties file name differs for each environment).
This is it. We have integrated JWT to our authentication and authorization process and now clients can use the token to make requests and server can authorize them.
Happy Coding!
Thank you for your contribution Lahiru Jayakodi Prabuddha Alahakoon Priyantha Buddhika Sandeepadng