CodeGym /Courses /Module 5. Spring /Lecture 103: Basics of JSON Web Token (JWT) and Its Struc...

Lecture 103: Basics of JSON Web Token (JWT) and Its Structure

Module 5. Spring
Level 18 , Lesson 2
Available

Welcome to one of the most interesting topics related to security in modern web apps! Today we're diving into the world of JSON Web Token (JWT), which became the de-facto standard for authentication and authorization, especially in microservice architectures. If this is the first time you hear the abbreviation JWT, don't worry — by the end of the lecture everything will be "encoded" for you, literally.


What is JSON Web Token (JWT)?

JSON Web Token (pronounced "J-W-T", but if you like puns you can call it "JavaTee") — is a compact, self-contained way to transmit information between two parties as a JSON object. JWT is used for authentication and for sending information in such a way that it can be verified without talking to the server.

Let's break down what makes it special:

  1. Compactness: a JWT is a string that's easy to send via URL, HTTP headers, or in the request body.
  2. Self-contained: the whole "context" (for example, user data) is contained inside the token.
  3. Security: tokens are signed so nobody can tamper with their data. If an algorithm like HMAC or RSA is used, the data is also signed (and in some setups encrypted).

JWT Structure

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


header.payload.signature

1. Header: contains metadata about the JWT, for example the token type (usually "JWT") and the signing algorithm (for example HMAC SHA256 or RSA).

Example:


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

This part of the token is encoded in Base64Url.

2. Payload: this is the main part of the token that holds data. It can contain arbitrary claims — assertions that the token makes to the system.

Example payload:


{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "exp": 1633024800
}

Note the following:

  • sub — user identifier.
  • exp (expiration) — the token's expiration time (number of seconds since the Unix epoch after which the token is no longer valid).

3. Signature: created to ensure the integrity of the token. It's calculated using the algorithm from the header, the encoded header and payload, and a secret key.

The signature formula looks like this:


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

Example of a "assembled" JWT:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

How JWT Works in Real Life

Authentication:

  1. The user sends their login credentials (username and password) to the server.
  2. The server verifies the credentials and, if everything is OK, generates a JWT.
  3. The JWT is returned to the user, who stores it (most often in localStorage or a cookie).
  4. On each request to the server the client sends the JWT in the Authorization header.

Example header:


Authorization: Bearer <your-jwt-token>

Authorization:

Using the information contained in the payload, the server can determine user roles (for example, admin, user) and grant access to protected resources.


Example of Using JWT in Spring Boot

Let's jump straight into practice. We'll create a Spring Boot app that generates and validates JWTs.

Dependency in pom.xml

Add the libraries required to work with JWT:


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

Generating a JWT

Create a service to generate tokens:


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

public class JwtUtil {

    private String secret = "mySecretKey"; // Never store the secret in code in real projects!

    // Token generation
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

    // Create token
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }
}

Decoding and Verifying a JWT

Add a method to verify the token:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtUtil {

    private String secret = "mySecretKey";

    // Проверка токена
    public Claims extractClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    public String extractUsername(String token) {
        return extractClaims(token).getSubject();
    }
}
}

Now the server can decode the token and extract useful info from it, for example the username.


Protecting a REST API

In Spring Security we can add a filter that checks the JWT on every request. Example filter:


import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

public class JwtFilter extends OncePerRequestFilter {

    private JwtUtil jwtUtil = new JwtUtil();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws java.io.IOException, jakarta.servlet.ServletException {

        String authorizationHeader = request.getHeader("Authorization");

        String token = null;
        String username = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            token = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(token);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // In a real case we'd verify the user (for example, via a database).
            // Verification code omitted for simplicity.
        }

        chain.doFilter(request, response);
    }
}

This filter is wired into your Spring Security configuration.


Common Mistakes When Working with JWT

  1. Secrets in code. Never store the secret key directly in the application. Use secret managers or environment variables.
  2. Non-refreshable tokens. If a token expires and you don't have a refresh token mechanism, users will be forced to re-authenticate.
  3. Payload bloat. Remember that all data in the token is sent to the client. Excessive information can slow things down and create security risks.

Keep in mind that JWT is a powerful tool, but it doesn't protect you from every security problem. Use it wisely, and you'll be ahead of the game!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION