Implementing JSON Web Token (JWT) Authentication using Spring Security | A Detailed Walkthrough

Sayan Das
Geek Culture
Published in
11 min readDec 23, 2021
Photo by FLY:D on Unsplash

Introduction

In this tutorial, you will learn to implement Json Web Token ( JWT ) authentication using Spring Boot and Spring Security. First, you’ll go through some basic theory regarding JWTs and then you will switch to hands-on mode and implement it in your Spring Application. I will explain every step in detail so stick till the very end.

MUST READ: If you are new to JWTs then keep reading. However, if you already have worked with JWTs or have knowledge about them and want to get started with the implementation, then click here.

What is JWT ?

JSON Web Token (JWT) is an open internet standard for sharing secure information between two parties. The token contains a JSON “payload” which is digitally signed ( with a private secret or public/private key ) using a cryptographic algorithm. The digital signature makes the token safe from tampering as a tampered token becomes invalid.

A JWT looks something like this

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.FGK5PCL49k3jfNCq6wZtn6T-uG9Dv4hOYIm55xTux8w

Quite a daunting piece of text, eh ?

If you look closely, you will notice two period (.) symbols in the JWT. These period symbols break up the JWT into three segments — Header, Payload and Signature.

The general form of a JWT is -> header.payload.signature.

Header

The first part of the token, the header is a JSON object containing two properties, typ(represents type of the token which is JWT)and alg(the algorithm to be used for signing).

{
"typ": "JWT",
"alg": "HS256"
}

This JSON object is Base64Url encoded to form the first part of the string.

Payload

The second part of the token, the payload contains the data, or “claims”, which you wish to transfer using this JWT. There are some defined claims such as

  • sub — Subject of the token
  • iss — Issuer of the token
  • exp — Expiration time of token
  • aud — Audience of the token

You can also add custom claims which both the ends have agreed on and provide share extra information about the token. In the below example, “role” is a custom claim.

{   
"sub": "john.doe@gmail.com",
"iss": "ABC Pvt Ltd",
"role": "ADMIN"
}

Signature

The signature is created by taking the encoded strings of the first two parts and passing it to the signing algorithm along with your secret.

HMACSHA256(   
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

The output is the JWT like the one you saw earlier

JWT Authentication Flow

Below diagram shows the flow of JWT authentication. As you can see in the diagram below, nothing is being stored in the server end.

Plan of Action

You will be building a REST API that exposes three endpoints —

  1. /api/auth/register — Creates and persists an User entity object and responds with a JWT built using this entity.
  2. /api/auth/login — Authenticates user credentials and generates a JWT
  3. /api/user/info — Protected route which responds with user information for the authenticated user

MUST READ: You can find the entire documented code in this GitHub repository -> here. I recommend you to download the project and go through the comments associated with almost every line because these comments provide detailed explanation of “everything” (including the basic stuff you are expected to know as prerequisite). You will find the instructions to download and use the source code in the GitHub link shared above.

Setting Up The Project

Time to do some hands-on and see all of this in action. To setup your Spring Boot project go to the starter website. Make sure you have Maven project and the latest version of Spring Boot selected (One without SNAPSHOT).

Add the following dependencies :-

  1. Spring Web : For building Web Applications

2. Spring Security : For adding security to your application

3. Spring Data JPA : For persistence

4. H2 Database : In-memory database for storing our application data

5. Lombok : Helps reducing boilerplate code using annotations

You can fill the artifact, name and description fields as you wish. Finally, it should look something like this.

Setting up Spring Boot project

Click on Generate and it’ll download a archive with the starter files. Extract the files and open them in your favorite IDE. This will the file structure of the project

com
└───example
└───springsecurityjwttutorial
│ SpringSecurityJwtTutorialApplication.java

├───controllers
│ AuthController.java
│ UserController.java

├───entity
│ User.java

├───models
│ LoginCredentials.java

├───repository
│ UserRepo.java

└───security
JWTFilter.java
JWTUtil.java
MyUserDetailsService.java
SecurityConfig.java

Execution of Plan

Entity

First let’s create our User entity. Create a new package entity and create a class User . This class defines the User POJO with the id, email and password fields. The @Entity annotation marks this class as an Entity and the other annotations are Lombok annotations to reduce boilerplate code ( such as adding getters, setters, constructors, etc. ).

NOTE: The @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) prevents the password field from being included in the JSON response.

package com.example.springsecurityjwttutorial.entity;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
public class User {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String email;

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;

}

Repository

Now that we have the entity let’s create a way to persist it. Create a new package repository and create a new interface UserRepo . We define a custom method findByEmail(String email) which retrieves an user entity based on their email. ( kinda self explanatory, eh ? \_(*.*)_/ )

package com.example.springsecurityjwttutorial.repository;

import com.example.springsecurityjwttutorial.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepo extends JpaRepository<User, Long> {
public Optional<User> findByEmail(String email);
}

Now let’s move on to the most important part — Security

Security

Before we do anything related to security, lets first create a class that will handle the creation and verification of the JWTs. Create a package security and in it, a class JWTUtil . To perform the JWT related operations, I recommend you use the java-jwt package. To include the package in your project add the following dependency to your pom.xml file and then rebuild the project.

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>

NOTE: It is better to copy the dependency from the github site as the latest version might be different at the time you are reading this. You can find the github site of the package here.

package com.example.springsecurityjwttutorial.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JWTUtil {

@Value("${jwt_secret}")
private String secret;

public String generateToken(String email) throws IllegalArgumentException, JWTCreationException {
return JWT.create()
.withSubject("User Details")
.withClaim("email", email)
.withIssuedAt(new Date())
.withIssuer("YOUR APPLICATION/PROJECT/COMPANY NAME")
.sign(Algorithm.HMAC256(secret));
}

public String validateTokenAndRetrieveSubject(String token)throws JWTVerificationException {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
.withSubject("User Details")
.withIssuer("YOUR APPLICATION/PROJECT/COMPANY NAME")
.build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("email").asString();
}

}

The generateToken method creates a token with subject, issuer, time of issue and a custom claim “email” and the second method verifies the same and extracts the email. Now in order for this to work, you need to provide a secret. The secret is a string (private to your project/team/company) used to sign your tokens. NEVER SHARE YOUR SECRET. Open the resources/application.properties file and add the following property.

jwt_secret=REPLACE_THIS_WITH_YOUR_SECRET

Make sure you choose a random and long string as your secret to ensure security of your tokens. One proven way is to let your cat run over the keyboard ( Just Kidding ;p )

Now let's create the user details service. A UserDetailsService is used to provide the custom implementation to fetch the user details of the user trying to authenticate into the application. This is done in the loadUserByUsername method. If no such user is found a UsernameNotFoundException is thrown. Create a class MyUserDetailsService which will implement the UserDetailsService interface.

package com.example.springsecurityjwttutorial.security;

import com.example.springsecurityjwttutorial.entity.User;
import com.example.springsecurityjwttutorial.repository.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Optional;

@Component
public class MyUserDetailsService implements UserDetailsService {

@Autowired private UserRepo userRepo;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<User> userRes = userRepo.findByEmail(email);
if(userRes.isEmpty())
throw new UsernameNotFoundException("Could not findUser with email = " + email);
User user = userRes.get();
return new org.springframework.security.core.userdetails.User(
email,
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
}
}

If you would like to more about howUserDetailsService works and how authentication in general works in Spring Security, check this out -> here.

Next let’s create a JWTFilter. The JWTFilterwill run for each request by implementing the OncePerRequestFilter interface and check if a Bearer token is present in the Authorization header. If a token is present, the token will be verified and the authentication data will set for the user for that request by setting the authentication property of the SecurityContext using the SecurityContextHolder . This is where your JWT comes into action and makes sure you are authenticated and can access protected resources that require you to be logged in/authenticated.

package com.example.springsecurityjwttutorial.security;

import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JWTFilter extends OncePerRequestFilter {

@Autowired private MyUserDetailsService userDetailsService;
@Autowired private JWTUtil jwtUtil;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if(authHeader != null && !authHeader.isBlank() && authHeader.startsWith("Bearer ")){
String jwt = authHeader.substring(7);
if(jwt == null || jwt.isBlank()){
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JWT Token in Bearer Header");
}else {
try{
String email = jwtUtil.validateTokenAndRetrieveSubject(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(email, userDetails.getPassword(), userDetails.getAuthorities());
if(SecurityContextHolder.getContext().getAuthentication() == null){
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}catch(JWTVerificationException exc){
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JWT Token");
}
}
}

filterChain.doFilter(request, response);
}
}

To put these all together and configure the security of the app, create a class SecurityConfig

package com.example.springsecurityjwttutorial.security;

import com.example.springsecurityjwttutorial.repository.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletResponse;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired private UserRepo userRepo;
@Autowired private JWTFilter filter;
@Autowired private MyUserDetailsService uds;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().disable()
.cors()
.and()
.authorizeHttpRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/user/**").hasRole("USER")
.and()
.userDetailsService(uds)
.exceptionHandling()
.authenticationEntryPoint(
(request, response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized")
)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

In the config, the important parts to notice are

  • The “auth” route requests are granted access by all ( This is quite obvious because you need to have access to the login and register routes )
  • The “user” route requests can only be accessed by authenticated users with the role of “USER” which is set in the MyUserDetailsService
  • UserDetailsService is configured with the custom MyUserDetailsService bean
  • Server is configured to reject the request as unauthorized when entry point is reached. If this point is reached it means that the current request requires authentication and no JWT token was found attached to the Authorization header of the current request.
  • The JWTFilter is added to the filter chain in order to process incoming requests.
  • Creating a bean for the password encoder
  • Exposing the bean of the authentication manager which will be used to run the authentication process in the AuthController

Models

Create a package models and create a class LoginCredentials . This class will be used to accept the login data from the request body. This class has two simple properties — email and password and the related Lombok annotations.

package com.example.springsecurityjwttutorial.models;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class LoginCredentials {

private String email;
private String password;

}

FINALLY, let’s bring them all together. Create a controllers package. In the package, create two classes

  • AuthController — Deals with the auth routes register and login.
package com.example.springsecurityjwttutorial.controllers;

import com.example.springsecurityjwttutorial.entity.User;
import com.example.springsecurityjwttutorial.models.LoginCredentials;
import com.example.springsecurityjwttutorial.repository.UserRepo;
import com.example.springsecurityjwttutorial.security.JWTUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

@Autowired private UserRepo userRepo;
@Autowired private JWTUtil jwtUtil;
@Autowired private AuthenticationManager authManager;
@Autowired private PasswordEncoder passwordEncoder;

@PostMapping("/register")
public Map<String, Object> registerHandler(@RequestBody User user){
String encodedPass = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPass);
user = userRepo.save(user);
String token = jwtUtil.generateToken(user.getEmail());
return Collections.singletonMap("jwt-token", token);
}

@PostMapping("/login")
public Map<String, Object> loginHandler(@RequestBody LoginCredentials body){
try {
UsernamePasswordAuthenticationToken authInputToken =
new UsernamePasswordAuthenticationToken(body.getEmail(), body.getPassword());

authManager.authenticate(authInputToken);

String token = jwtUtil.generateToken(body.getEmail());

return Collections.singletonMap("jwt-token", token);
}catch (AuthenticationException authExc){
throw new RuntimeException("Invalid Login Credentials");
}
}


}

The register method persists the entity and then responds with a JWT and the login method authenticates the login credentials and then responds with a JWT.

  • UserController — Deals with the user routes
package com.example.springsecurityjwttutorial.controllers;

import com.example.springsecurityjwttutorial.entity.User;
import com.example.springsecurityjwttutorial.repository.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/user")
public class UserController {

@Autowired private UserRepo userRepo;

@GetMapping("/info")
public User getUserDetails(){
String email = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userRepo.findByEmail(email).get();
}


}

Notice that the email of the user is not taken as input. It is extracted from the SecurityContext as the email was set in the JWTFilter

Time to Execute

On running the application on IntelliJ IDEA, this is the output I get. Looks like everything is fine.

Starting the Spring Boot App

Now let’s make some requests. To make the requests I’ll be using Postman.

First let’s hit the register route.

HTTP Request for Registering a User and generating JWT

HURRAY !! You just generated your first JWT using Spring Security. Now let’s test the protected endpoint -> the user endpoint. Copy this token as you will need it shortly.

Let’s create a request in a new tab to the user endpoint without adding the token.

As you can see the request is rejected with a “Unauthorized” status.

Now let’s add the token. To do so, go to the Authorization tab and select Bearer token from the dropdown and paste the copied token in the provided box.

Now if you send the request again, you’ll see this

VOILA !! Now you get the user data. You can test the login route on your own.

Conclusion

SO THAT’S IT. Now you can completely implement a JWT Authentication Flow using Spring Boot Security and Spring Boot.

Support

If you liked the article, do follow and clap 👏🏻 for it and if this article helped you do consider supporting me by buying me a coffee . It will help me to write more such articles related to tech and coding and keep giving back to the community in the only way I can.

SO … SEE YA … UNTIL NEXT TIME !! 🙋🏻‍♂️

More Articles By Me

  1. Demystifying the Folder Structure of a React App ( 58k+ views )
  2. Spring Security Authentication Flow
  3. React Redux Deep Dive

--

--

Sayan Das
Geek Culture

I am Software Developer who loves to learn constantly and build things which others can use to make their lives easier.