Spring Security - JWT



Contents

  • JWT Introduction and overview
  • Getting started with Spring Security using JWT(Practical Guide)

JWT Introduction and overview

JSON Web Token or JWT, as it is more commonly called, is an open Internet standard (RFC 7519) for securely transmitting trusted information between parties in a compact way. The tokens contain claims that are encoded as a JSON object and are digitally signed using a private secret or a public key/private key pair. They are self-contained and verifiable as they are digitally signed. JWT’s can be signed and/or encrypted. The signed tokens verify the integrity of the claims contained in the token, while the encrypted ones hide the claims from other parties.

JWT’s can also be used for the exchange of information though they more commonly used for authorization as they offer a lot of advantages over session management using in-memory random tokens. The biggest of them being the enabling the delegation of authentication logic to a third-party server like AuthO etc.

A JWT token is divided into 3 parts namely – header, payload, and signature in the format of

[Header].[Payload].[Signature]
  • Header − The Header of a JWT token contains the list cryptographic operations that are applied to the JWT. This can be the signing technique, metadata information about the content-type and so on. The header is presented as a JSON object which is encoded to a base64URL. An example of a valid JWT header would be

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

Here, “alg” gives us information about the type of algorithm used and “typ gives us the type of the information.

  • Payload − The payload part of JWT contains the actual data to be transferred using the token. This part is also known as the “claims” part of the JWT token. The claims can be of three types – registered, public and private.

  • The registered claims are the ones which are recommended but not mandatory claims such as iss(issuer), sub(subject), aud(audience) and others.

  • Public claims are those that are defined by those using the JWTs.

  • Private claims or custom claims are user-defined claims created for the purpose of sharing the information between the concerned parties.

Example of a payload object could be.

{ "sub": "12345", "name": "Johnny Hill", "admin": false }

The payload object, like the header object is base64Url encoded as well and this string forms the second part of the JWT.

  • Signature− The signature part of the JWT is used for the verification that the message wasn’t changed along the way. If the tokens are signed with private key, it also verifies that the sender is who it says it is. It is created using the encoded header, encoded payload, a secret and the algorithm specified in the header. An example of a signature would be.

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

If we put the header, payload and signature we get a token as given below.

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlfQ.gWDlJdpCTIHVYKkJSfAVNUn0ZkAjMxskDDm-5Fhe
WJ7xXgW8k5CllcGk4C9qPrfa1GdqfBrbX_1x1E39JY8BYLobAfAg1fs_Ky8Z7U1oCl6HL63yJq_
wVNBHp49hWzg3-ERxkqiuTv0tIuDOasIdZ5FtBdtIP5LM9Oc1tsuMXQXCGR8GqGf1Hl2qv8MCyn
NZJuVdJKO_L3WGBJouaTpK1u2SEleVFGI2HFvrX_jS2ySzDxoO9KjbydK0LNv_zOI7kWv-gAmA
j-v0mHdJrLbxD7LcZJEGRScCSyITzo6Z59_jG_97oNLFgBKJbh12nvvPibHpUYWmZuHkoGvuy5RLUA

Now, this token can be used in the Authorization header using the Bearer schema as.

Authorization − Bearer <token>

The use of JWT token for authorization is the most common of its applications. The token is usually generated in the server and sent to the client where it is stored in the session storage or local storage. To access a protected resource the client would send the JWT in the header as given above. We will see the JWT implementation in Spring Security in the section below.

Getting Started with Spring Security using JWT

The application we are going to develop will handle basic user authentication and authorization with JWT’s. Let’s get started by going to start.spring.io where we will create a Maven application with the following dependencies.

  • Spring Web
  • Spring Security
Maven Project Java

We generate the project and when it is downloaded, we extract it to a folder of our choice. We can then use any IDE of our choice. I am going to use Spring Tools Suite 4 as it is most optimized for Spring applications.

Apart from the above-mentioned dependencies we are also going to include the jwt dependency from io.jsonwebtoken from the Maven central repository as it is not included in the spring initializer. This dependency takes care of all operations involving the JWT including building the token, parsing it for claims and so on.

<dependency> 
   <artifactId>jjwt</artifactId> 
   <version>0.9.1</version> 
</dependency>

Our pom.xml file should now look similar to this.

<?xml version="1.0" encoding="UTF-8"?> 
<project xmlns="http://maven.apache.org/POM/4.0.0" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
   https://maven.apache.org/xsd/maven-4.0.0.xsd"> 
   <modelVersion>4.0.0</modelVersion> 
   <parent> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-parent</artifactId> 
      <version>2.3.1.RELEASE<version> 
      <relativePath /> 
      <!-- lookup parent from repository --> 
   </parent> 
   <groupId>com.spring.security</groupId> 
   <artifactId>jwtbasic</artifactId>
   <version>0.0.1-SNAPSHOT</version> 
   <name>jwtbasic</name> 
   <description>Demo project for Spring Boot</description> 
   <properties> 
      <java.version>1.8</java.version> 
   </properties> 
   <dependencies> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-security</artifactId> 
      </dependency> 
      <dependency>
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-web</artifactId> 
      </dependency> 
      <dependency> 
         <groupId>io.jsonwebtoken</groupId> 
         <artifactId>jjwt</artifactId> 
         <version>0.9.1</version> 
      </dependency>
      <dependency> 
         <groupId>javax.xml.bind</groupId> 
         <artifactId>jaxb-api</artifactId> 
      </dependency> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-test</artifactId> 
         <scope>test</scope> 
         <exclusions> 
      <exclusion> 
         <groupId>org.junit.vintage</groupId> 
         <artifactId>junit-vintage-engine</artifactId> 
         </exclusion> 
      </exclusions> 
      </dependency> 
      <dependency> 
      <groupId>org.springframework.security</groupId> 
      <artifactId>spring-security-test</artifactId> 
      <scope>test<scope>
      <dependency> 
   </dependencies> 
      <build> 
      <plugins> 
         <plugin> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-maven-plugin</artifactId> 
         </plugin> 
      </plugins> 
   </build> 
</project>

Now that our project is set up we are going to create our controller class Hello Controller which exposes a Get endpoint.

package com.spring.security.jwtbasic.controllers; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RestController; 
@RestController 
public class HelloController {
   @GetMapping("/hello") 
   public String hello() { 
      return "hello"; 
   } 
}

Now we are going to create a package called config where we add the configuration class that extends the WebSecurityConfigurerAdapter class of Spring Security. This will provide us with all the required functions and definitions for project configuration and security of our application. For now, we provide the BcryptPasswordEncoder instance by implementing a method that generates the same. We annotate the method with @Bean to add to our Spring Context.

package com.spring.security.jwtbasic.config; 
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.authentication.builders.AuthenticationManagerBuilder; 
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService; 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 
import org.springframework.security.crypto.password.PasswordEncoder; 
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 
import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint; 
import com.spring.security.jwtbasic.jwtutils.JwtFilter; 
@Configuration 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   @Bean 
   public PasswordEncoder passwordEncoder() { 
      return new BCryptPasswordEncoder(); 
   } 
}

The JWT includes a secret which we will define in our application.properties file as given below.

secret=somerandomsecret

Now let’s create a package called jwtutils. This package is going to contain all classes and interface related to JWT operations, which will include.

  • Generating token
  • Validating token
  • Checking the signature
  • Verifying claims and permissions

In this package, we create our first class called Token Manager. This class will be responsible for the creation and validation of tokens using io.jsonwebtoken.Jwts.

package com.spring.security.jwtbasic.jwtutils; 
import java.io.Serializable; 
import java.util.Base64; 
import java.util.Date; 
import java.util.HashMap; 
import java.util.Map; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.security.core.userdetails.UserDetails; 
import org.springframework.stereotype.Component; 
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; 
import io.jsonwebtoken.SignatureAlgorithm; 
@Component 
public class TokenManager implements Serializable {
   /** 
   *
   */ 
   private static final long serialVersionUID = 7008375124389347049L; public static final long TOKEN_VALIDITY = 10 * 60 * 60; @Value("${secret}") 
   private String jwtSecret; 
   public String generateJwtToken(UserDetails userDetails) { 
      Map<String, Object> claims = new HashMap<>(); 
      return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()) 
         .setIssuedAt(new Date(System.currentTimeMillis())) 
         .setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY * 1000)) 
         .signWith(SignatureAlgorithm.HS512, jwtSecret).compact(); 
   } 
   public Boolean validateJwtToken(String token, UserDetails userDetails) { 
      String username = getUsernameFromToken(token); 
      Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
      Boolean isTokenExpired = claims.getExpiration().before(new Date()); 
      return (username.equals(userDetails.getUsername()) && !isTokenExpired); 
   } 
   public String getUsernameFromToken(String token) {
      final Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); 
      return claims.getSubject(); 
   } 
}

Here, as all tokens should have an expiration date, we start with a token validity constant. Here, we want our token to be valid for 10 minutes after generation. We will use this value when we generate our token. Then we extract the value of our singing key from the application.properties file into our jwtSecret field using the @Value annotation.

We have two methods here −

  • generateJwtToken() − This method is used to generate a token on successful authentication by the user. To create the token here we use the username, issue date of token and the expiration date of the token. This will form the payload part of the token or claims as we had discussed earlier. To generate the token we use the builder() method of Jwts. This method returns a new JwtBuilder instance that can be used to create compact JWT serialized strings.

To set the claims we use the setClaims() method and then set each of the claims. For this token we have setSubject(username), issue date and expiration date. We can also put our custom claims as we had discussed above. This can be any value we want which might include user role, user authorities and so on.

Then we set the signature part of the token. This is done using the signWith() method, we set the hashing algorithm we prefer to use and the secret key. Then we use thecompact() method that builds the JWT and serializes it to a compact, URL-safe string according to the JWT Compact Serialization rules.

  • validateJwtToken() − Now that the generation of the token is taken care of, we should focus on the process of validation of the token when it comes as a part of requests. To validate the token means to verify the request is an authenticated one and that the token is the one that was generated and sent to the user. Here, we need to parse the token for the claims such as username, roles, authorities, validity period etc.

To validate the token we need to parse it first. This is done using the parser() method of Jwts. We then need to set the signing key that we used to generate the token and then use parseClaimsJws() method on the token to parse the compact serialized JWS string based on the builder’s current configuration state and return the resulting Claims JWS instance. The getBody() method is then used to return the claims instance that was used while generating the token.

From this obtained claims instance, we extract the subject and the expiry date to verify the validity of the token. The username should be the username of the user and the token should not be expired. If both these conditions are met, we return true, which signifies that the token is valid.

The next class we would be creating is the JwtUserDetailsService. This class will extend the UserDetailsService of Spring security and we will implement the loadUserByUsername() method as given below −

package com.spring.security.jwtbasic.jwtutils; 
import java.util.ArrayList; 
import org.springframework.security.core.userdetails.User; 
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.Service; 
@Service
public class JwtUserDetailsService implements UserDetailsService { 
   @Override 
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      if ("randomuser123".equals(username)) { 
         return new User("randomuser123", 
            "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6", 
            new ArrayList<>()); 
      } else { 
         throw new UsernameNotFoundException("User not found with username: " + username); 
      } 
   } 
}

Here, since this is a basic application for the sole purpose of the demonstration of JWT authentication, we have resorted to a set of our user details, instead of using a database. We have given the username as “randomuser123” and encoded the password, which is “password” as “$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6” for our convenience.

Next, we create classes for our Request and Response models. These models determine how our request and response formats would be for authentication. The first snapshot given below is the request model. As we can see, we shall be accepting two properties – username and password in our request.

package com.spring.security.jwtbasic.jwtutils.models;
import java.io.Serializable; 
public class JwtRequestModel implements Serializable { 
   /** 
   * 
   */ 
   private static final long serialVersionUID = 2636936156391265891L; 
   private String username; 
   private String password; 
   public JwtRequestModel() { 
   } 
   public JwtRequestModel(String username, String password) { 
      super(); 
      this.username = username; this.password = password; 
   } 
   public String getUsername() { 
      return username;
   } 
   public void setUsername(String username) { 
      this.username = username; 
   } 
   public String getPassword() { 
      return password; 
   } 
   public void setPassword(String password) { 
      this.password = password; 
   } 
}

Below is the code for response model on successful authentication. As we can see, we will be sending the token back to the user on successful authentication.

package com.spring.security.jwtbasic.jwtutils.models; 
import java.io.Serializable; 
public class JwtResponseModel implements Serializable {
   /**
   *
   */
   private static final long serialVersionUID = 1L;
   private final String token;
   public JwtResponseModel(String token) {
      this.token = token;
   }
   public String getToken() {
      return token;
   }
}

For authentication now, let’s create a controller as given below.

package com.spring.security.jwtbasic.jwtutils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.spring.security.jwtbasic.jwtutils.models.JwtRequestModel;
import com.spring.security.jwtbasic.jwtutils.models.JwtResponseModel;
@RestController
@CrossOrigin
public class JwtController {
   @Autowired
   private JwtUserDetailsService userDetailsService;
   @Autowired
   private AuthenticationManager authenticationManager;
   @Autowired
   private TokenManager tokenManager;
   @PostMapping("/login")
   public ResponseEntity<> createToken(@RequestBody JwtRequestModel
   request) throws Exception {
      try {
         authenticationManager.authenticate(
            new
            UsernamePasswordAuthenticationToken(request.getUsername(),
            request.getPassword())
         );
      } catch (DisabledException e) {
         throw new Exception("USER_DISABLED", e);
      } catch (BadCredentialsException e) {
         throw new Exception("INVALID_CREDENTIALS", e);
      }
      final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
      final String jwtToken = tokenManager.generateJwtToken(userDetails);
      return ResponseEntity.ok(new JwtResponseModel(jwtToken));
   }
}

If we go through the code we can see that, we have autowired three dependencies namely, JwtUserDetailsService, AuthenticationManager and TokenManager. While we have already seen the implementation of JwtUserDetailsService and TokenManager classes above, the authentication manager bean is one we shall be creating in our WebSecurityConfig class.

AuthenticationManager class will take care of our authentication. We shall be using the UsernamePasswordAuthenticationToken model for authentication of the request. If authentication succeeds we shall generate a JWT for the user, which can be sent in the Authorization header of the subsequent requests to get any resource.

As we can see, we are using the loadUserByUsername() method of our JwtUserDetailsService class and the generateJwtToken() from TokenManager class.

This generated JWT is sent to the user as a response on successful authentication as mentioned above.

Now it’s time we created our Filter. The filter class will be used to track our requests and detect if they contain the valid token in the header. If the token is valid we let the request proceed otherwise we send a 401 error (Unauthorized).

package com.spring.security.jwtbasic.jwtutils;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
@Component
public class JwtFilter extends OncePerRequestFilter {
   @Autowired
   private JwtUserDetailsService userDetailsService;
   @Autowired
   private TokenManager tokenManager;
   @Override
   protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
      
      String tokenHeader = request.getHeader("Authorization");
      String username = null;
      String token = null;
      if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
         token = tokenHeader.substring(7);
         try {
            username = tokenManager.getUsernameFromToken(token);
         } catch (IllegalArgumentException e) {
            System.out.println("Unable to get JWT Token");
         } catch (ExpiredJwtException e) {
            System.out.println("JWT Token has expired");
         }
      } else {
         System.out.println("Bearer String not found in token");
      }
      if (null != username &&SecurityContextHolder.getContext().getAuthentication() == null) {
         UserDetails userDetails = userDetailsService.loadUserByUsername(username);
         if (tokenManager.validateJwtToken(token, userDetails)) {
            UsernamePasswordAuthenticationToken
            authenticationToken = new UsernamePasswordAuthenticationToken(
            userDetails, null,
            userDetails.getAuthorities());
            authenticationToken.setDetails(new
            WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
         }
      }
      filterChain.doFilter(request, response);
   }
}

As we can see above, we have autowired the JwtUserDetailsService and TokenManager classes here as well. We have extended the OncePerRequestFilter of SpringSecurity which makes sure the filter is run for every request. We have provided our implementation to the overridden method doFilterInternal() of the OncePerRequestFilter class.

The method here extracts the token from the header and validates it with the help of validateJwtToken() method of our TokenManager class. During validation, it checks for the username and the expiration date. If both the values are valid, we save the authentication in our Spring Security context and let the code proceed to the next filter in our filter chain. If any of the validation fails or there is an issue with the token or if the token is not found we throw the appropriate exceptions and send back an appropriate response while blocking the request from moving ahead.

Having created the filter for our requests, we now create the JwtAutheticationEntryPoint class. This class extends Spring’s AuthenticationEntryPoint class and rejects every unauthenticated request with an error code 401 sent back to the client. We have overridden the commence() method of AuthenticationEntryPoint class to do that.

package com.spring.security.jwtbasic.jwtutils;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint,
Serializable {
   @Override
   public void commence(HttpServletRequest request, HttpServletResponse
   response,
   AuthenticationException authException) throws
   IOException, ServletException {
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
   "Unauthorized");
      }
}

Now, let’s get back to our WebSecurityConfig class and finish the rest of our configuration. If we remember, we are going to require our AuthenticationManager bean for our Jwt controller class and add the filter we just created to our configuration. We are also going to configure which requests are to be authenticated and which are not to be. We shall also add the AuthenticationEntryPoint to our requests to send back the 401 error response. Since, we also do not need to maintain session variables while using jwt we can make our session STATELESS.

package com.spring.security.jwtbasic.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint;
import com.spring.security.jwtbasic.jwtutils.JwtFilter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   @Autowired
   private JwtAuthenticationEntryPoint authenticationEntryPoint;
   @Autowired
   private UserDetailsService userDetailsService;
   @Autowired
   private JwtFilter filter;
   @Bean
   public PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
   }
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
   }
   @Bean
   @Override
   public AuthenticationManager authenticationManagerBean() throws
   Exception {
      return super.authenticationManagerBean();
   }
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
      .authorizeRequests().antMatchers("/login").permitAll()
      .anyRequest().authenticated()
      .and()
      .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
      .and()
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
      http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
   }
}

As we can see, we have done all of that, and now our application is ready to go. Let’s start the application and use postman for making our requests.

Postman Body

Here we have made our first request to get the token, and as we can see on providing the correct username/password combination we get back our token.

Now using that token in our header, let’s call the /hello endpoint.

Postman Authorization Body

As we can see, since the request is authenticated, we get the desired response back. Now, if we tamper with the token or do not send the Authorization header, we will get a 401 error as configured in our application. This ensures that the protection our request using the JWT.

Advertisements