Jwt Token

11 minute read

Published:

I’ve been busy working on the rebuttal for UAI 2025 these days. As a result, I haven’t written any blogs, nor have I developed any applications. Yesterday, our group held a meeting to discuss the new features of our application on Table Tennis. Unfortunately, I missed it and forgot to inform my mentor. Fortunately, F senpai covered for me. So, as soon as I finish the rebuttal, I offered to assist him with his graduation project as a way of repaying the favor.

Project Background

F senpai’s graduation project is a website. The back-end of the project is based on SpringBoot, and the front-end is based on Vue. I’m in charge of developing the back-end. Since I’ve developed several back-ends using SpringBoot, I’m quite familiar with the SpringBoot framework and general back-end design. The first step is to build the login and registration functionality, which is where Jwt Token comes into play.

What is Jwt Token

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

Structure of JWT

A JWT token consists of three parts, separated by dots (.):

  1. Header: It typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA. For example:
{
    "alg": "HS256",
    "typ": "JWT"
}

This JSON is then Base64Url-encoded to form the first part of the JWT.

  1. Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims.
    • Registered claims: These are a set of predefined claims which are not mandatory but recommended, like iss (issuer), sub (subject), aud (audience), etc.
    • Public claims: These can be defined at will by those using JWTs.
    • Private claims: These are custom claims created to share information between parties that agree on using them. An example of a payload could be:
{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}

This JSON is also Base64Url-encoded to form the second part of the JWT.

  1. Signature: To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that. For example, if you are using the HMAC SHA256 algorithm, the signature will be created in the following way:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

The signature is used to verify that the message wasn’t changed along the way, and in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

Use cases of JWT

  • Authentication: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
  • Information Exchange: JWTs are a good way of securely transmitting information between parties because they can be signed, which means you can be sure that the senders are who they say they are. Additionally, the structure of a JWT allows you to verify that the content hasn’t been tampered with.

Key factors of Jwt Token

Security

  • Secret Key: The security of a JWT heavily relies on the secrecy of the signing key. If an attacker gets hold of the secret key used to sign the JWT, they can create and sign their own tokens, which can lead to unauthorized access. In a production environment, the secret key should be stored securely, preferably in environment variables.
  • Algorithm Selection: Choosing the right signing algorithm is crucial. For example, symmetric algorithms like HMAC (HS256, HS384, HS512) use the same key for signing and verifying the token, which is simple but requires that the key be shared securely between the parties. Asymmetric algorithms like RSA (RS256, RS384, RS512) use a public-private key pair, which is more secure in some scenarios but also more complex to implement.

Expiration

  • Token Expiration: JWTs should have an expiration time set. This helps in reducing the risk of a compromised token being used indefinitely. The exp claim in the payload can be used to specify the expiration time. For example, if you set the expiration time to 1 hour, after that hour, the token will no longer be valid, and the user will need to log in again to obtain a new token.

Performance

  • Token Size: Since JWTs are sent with every request, a large token size can have a negative impact on performance, especially on mobile devices or in high-traffic applications. Therefore, it’s important to keep the payload of the JWT as small as possible, only including the necessary information.

How to use Jwt Token in a back end project

We will use a Spring Boot project to demonstrate how to use JWT for authentication and authorization.

Step 1: Add Dependencies

First, add the necessary dependencies to your pom.xml file:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Step 2: Configure JWT Properties

Create a JwtUtils class to handle JWT generation, validation, and extraction of information:

package org.example.pingpongorganize.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtils {

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

    @Value("${jwt.expiration}")
    private Long expiration;

    // Generate a JWT token based on the username
    public String generateToken(String username) {
        // Claims are statements about an entity (typically, the user) and additional data
        Map<String, Object> claims = new HashMap<>();
        // Create a key for signing the JWT
        Key key = Keys.hmacShaKeyFor(secret.getBytes());
        return Jwts.builder()
               .setClaims(claims)  // Set the claims in the JWT payload
               .setSubject(username)  // Set the subject of the JWT, usually the username
               .setIssuedAt(new Date(System.currentTimeMillis()))  // Set the issuance time of the JWT
               .setExpiration(new Date(System.currentTimeMillis() + expiration))  // Set the expiration time of the JWT
               .signWith(key, SignatureAlgorithm.HS256)  // Sign the JWT using the specified key and algorithm
               .compact();  // Compact the JWT into a string
    }

    // Validate whether the JWT token is valid and belongs to the specified user
    public boolean validateToken(String token, String username) {
        final String tokenUsername = getUsernameFromToken(token);
        return (tokenUsername.equals(username) && !isTokenExpired(token));
    }

    // Extract the username from the JWT token
    public String getUsernameFromToken(String token) {
        final Claims claims = getClaimsFromToken(token);
        return claims.getSubject();  // Return the subject of the claims, which is the username in this case
    }

    // Parse the JWT token to get its claims
    private Claims getClaimsFromToken(String token) {
        return Jwts.parser()
               .setSigningKey(secret)  // Set the signing key for verification
               .parseClaimsJws(token)  // Parse the JWT and get the body (claims)
               .getBody();
    }

    // Check if the JWT token has expired
    private boolean isTokenExpired(String token) {
        final Date expiration = getClaimsFromToken(token).getExpiration();
        return expiration.before(new Date());
    }
}

Step 3: Create a Login Endpoint

In the UserController class, create a login endpoint that generates a JWT token upon successful authentication:

package org.example.pingpongorganize.controller;

import io.swagger.annotations.ApiOperation;
import org.example.pingpongorganize.dto.LoginRequestDTO;
import org.example.pingpongorganize.service.UserService;
import org.example.pingpongorganize.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtUtils jwtUtils;

    @ApiOperation("User Login")
    @PostMapping("/user/login")
    public ResponseEntity<?> login(@RequestBody LoginRequestDTO loginRequest) {
        String username = loginRequest.getUsername();
        String password = loginRequest.getPassword();

        boolean isValid = userService.validateUser(username, password);

        if (isValid) {
            // Generate a JWT token if the credentials are valid
            String token = jwtUtils.generateToken(username);
            // Create a response map to hold the token and a success message
            Map<String, String> response = new HashMap<>();
            response.put("token", token);
            response.put("message", "Login successful!");
            return ResponseEntity.ok(response);
        } else {
            return ResponseEntity.status(401).body("Incorrect username or password!");
        }
    }
}

Step 4: Create an Interceptor to Validate Tokens

Create a LoginCheckInterceptor class to intercept requests and validate the JWT token:

package org.example.pingpongorganize.interceptor;

import org.example.pingpongorganize.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtils jwtUtils;

    // This method is called before the request is processed by the handler
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Get the Authorization header from the request
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            // Extract the actual JWT token from the Authorization header
            token = token.substring(7);
            // Get the username from the token
            String username = jwtUtils.getUsernameFromToken(token);
            // Check if the token is valid for the username
            return jwtUtils.validateToken(token, username);
        }
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }
}

Step 5: Configure the Interceptor

In a configuration class, configure the interceptor to intercept requests:

package org.example.pingpongorganize.config;

import org.example.pingpongorganize.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// This configuration class is used to configure the Spring MVC interceptors
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor)
               // Intercept all requests
               .addPathPatterns("/**")
               // Exclude the login and registration endpoints from interception
               .excludePathPatterns("/user/login", "/user/register");
    }
}

In this example, when a user logs in, the server generates a JWT token and sends it back to the client. The client then includes this token in the Authorization header of subsequent requests. The LoginCheckInterceptor intercepts these requests, validates the token, and allows or denies access based on the token’s validity.

This is a basic implementation of using JWT in a Spring Boot back-end project. In a real-world scenario, you may need to handle more edge cases, such as token refresh, and integrate with other security mechanisms.

Summary and a Dash of Fun

In a nutshell, JSON Web Tokens (JWT) are a compact and secure way to transmit data between parties. Comprising a header, payload, and signature, JWTs are crucial for authentication and information exchange in web apps. Key factors like security (secrecy of the signing key and right algorithm choice), expiration (to prevent long - term misuse), and performance (keeping the token size small) must be considered.

We saw a practical implementation in a Spring Boot project, where we added dependencies, crafted a JwtUtils class for token operations, set up a UserController for login and token issuance, and used an interceptor to guard protected endpoints.

Now, let’s talk about F senpai. Rumor has it that F senpai is so good at coding that he can make bugs apologize for existing! When he found out the author was helping with the project, he was like a superhero getting a sidekick. But instead of a cool sidekick, he got a coding buddy who sometimes accidentally turns the code into a “spaghetti monster.” Together, they’ve been on a wild coding adventure, fighting the evil forces of syntax errors and logic glitches. And thanks to JWT, they’re making sure only the good guys (authenticated users) can enter their digital kingdom. So here’s to F senpai, the coding wizard, and the author, the slightly clumsy but enthusiastic helper, and to JWT for keeping their web project secure and sound!