JWT Token Authentication in Spring Boot

In modern application development, stateless authentication using JWT (JSON Web Tokens) has become the de facto standard for securing REST APIs. This blog provides a complete walkthrough of implementing JWT token-based authentication in a Spring Boot application.

What is JWT?

JWT (JSON Web Token) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

It is widely used for authorization, especially in stateless microservices architecture where session-based mechanisms are not ideal.

Structure of a JWT

A JWT consists of three parts:

  • Header: Contains metadata about the type of token and algorithm used.
  • Payload: Contains the claims or user data (e.g., username, roles).
  • Signature: Used to verify the integrity of the token using a secret key or public/private key pair.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. 
eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjEyODgzMjAwfQ. 
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    

Why Use JWT for Authentication?

Compared to traditional session-based authentication, JWT offers several advantages:

  • Stateless: No session storage needed on the server.
  • Compact: Transmitted via HTTP headers or query parameters.
  • Secure: Digitally signed to ensure integrity.
  • Scalable: Easy to implement in distributed systems and microservices.
  • Cross-domain: Works across different domains and platforms.

How JWT Works

The lifecycle of JWT-based authentication in a Spring Boot application is:

  1. Client sends login credentials to the authentication endpoint.
  2. Server verifies credentials and generates a signed JWT.
  3. Server returns the JWT to the client in the response body or header.
  4. Client includes the JWT in the Authorization header of each subsequent request.
  5. Server validates the token, extracts user data, and allows or denies access.

Authorization Header Format:


Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    

JWT vs Session-Based Authentication

Feature Session-Based JWT-Based
Storage Stored on server Stored on client (token)
Scalability Less scalable (server memory required) Highly scalable (stateless)
Security Susceptible to CSRF Secure with HMAC or RSA signature
Cross-domain Limited Flexible

Prerequisites for JWT in Spring Boot

Before implementing JWT authentication, make sure you have the following:

  • Java 8+
  • Spring Boot 2.6 or above
  • Dependencies: spring-boot-starter-security, spring-boot-starter-web, and jjwt or java-jwt

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

Creating the JWT Utility Class

The JwtUtil class is responsible for:

  • Generating a JWT token
  • Validating a JWT token
  • Extracting username or claims from token

JwtUtil.java


@Component
public class JwtUtil {

  private final String SECRET_KEY = "your-secret-key";

  public String extractUsername(String token) {
    return extractClaim(token, Claims::getSubject);
  }

  public Date extractExpiration(String token) {
    return extractClaim(token, Claims::getExpiration);
  }

  public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = extractAllClaims(token);
    return claimsResolver.apply(claims);
  }

  private Claims extractAllClaims(String token) {
    return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
  }

  private Boolean isTokenExpired(String token) {
    return extractExpiration(token).before(new Date());
  }

  public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    return createToken(claims, userDetails.getUsername());
  }

  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))
      .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
      .compact();
  }

  public Boolean validateToken(String token, UserDetails userDetails) {
    final String username = extractUsername(token);
    return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
  }
}
    

Authentication Request & Response Models

You’ll need two simple models to handle authentication requests and responses.

AuthenticationRequest.java


public class AuthenticationRequest {
  private String username;
  private String password;

  // Constructors, getters, setters
}
    

AuthenticationResponse.java


public class AuthenticationResponse {
  private String jwt;

  public AuthenticationResponse(String jwt) {
    this.jwt = jwt;
  }

  public String getJwt() {
    return jwt;
  }
}
    

Creating the Authentication Controller

This REST controller will expose the /authenticate endpoint that handles login requests and returns a JWT token.


@RestController
public class AuthenticationController {

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private JwtUtil jwtUtil;

  @Autowired
  private UserDetailsService userDetailsService;

  @PostMapping("/authenticate")
  public ResponseEntity<?> createAuthenticationToken(
      @RequestBody AuthenticationRequest authRequest) throws Exception {

    try {
      authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(
            authRequest.getUsername(), authRequest.getPassword()));
    } catch (BadCredentialsException e) {
      throw new Exception("Incorrect username or password", e);
    }

    final UserDetails userDetails =
        userDetailsService.loadUserByUsername(authRequest.getUsername());

    final String jwt = jwtUtil.generateToken(userDetails);

    return ResponseEntity.ok(new AuthenticationResponse(jwt));
  }
}
    

Configuring AuthenticationManager

Spring Boot does not auto-configure an AuthenticationManager bean in newer versions. You must define it manually.


@Configuration
public class AppConfig {

  @Bean
  public AuthenticationManager authenticationManager(
      AuthenticationConfiguration configuration) throws Exception {
    return configuration.getAuthenticationManager();
  }
}
    

Custom UserDetailsService

You can define your own UserDetailsService to fetch user credentials from a database or in-memory.


@Service
public class MyUserDetailsService implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String username)
      throws UsernameNotFoundException {
    return new User("admin", "{noop}password", new ArrayList<>());
  }
}
    

Creating a JWT Request Filter

The JWT filter ensures every incoming HTTP request is inspected for a valid token. If valid, it sets the authentication in the Spring Security context.

JwtRequestFilter.java


@Component
public class JwtRequestFilter extends OncePerRequestFilter {

  @Autowired
  private MyUserDetailsService userDetailsService;

  @Autowired
  private JwtUtil jwtUtil;

  @Override
  protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain chain)
      throws ServletException, IOException {

    final String authHeader = request.getHeader("Authorization");

    String username = null;
    String jwt = null;

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

    if (username != null &&
        SecurityContextHolder.getContext().getAuthentication() == null) {

      UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

      if (jwtUtil.validateToken(jwt, userDetails)) {
        UsernamePasswordAuthenticationToken authToken =
            new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());

        authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authToken);
      }
    }

    chain.doFilter(request, response);
  }
}
    

Configuring SecurityFilterChain

In Spring Boot 2.7+ and Spring Security 5.7+, use a bean of type SecurityFilterChain instead of extending WebSecurityConfigurerAdapter.

SecurityConfig.java


@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Autowired
  private JwtRequestFilter jwtRequestFilter;

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeHttpRequests()
        .requestMatchers("/authenticate").permitAll()
        .anyRequest().authenticated()
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

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

This setup ensures:

  • CSRF protection is disabled for APIs
  • /authenticate is publicly accessible
  • All other endpoints require a valid JWT
  • Stateless session policy is enforced

Protecting REST Endpoints

Now that Spring Security is set up, any request to protected endpoints will go through the filter, validate the token, and establish the security context.

Example secure REST controller:


@RestController
public class HelloController {

  @GetMapping("/hello")
  public String hello() {
    return "Hello from secured endpoint!";
  }
}
    

When called without a token:

HTTP 403 Forbidden

When called with a valid JWT in the header:


Authorization: Bearer <your-jwt-token>
    

You will receive:

Hello from secured endpoint!

Testing Your JWT Setup

Use Postman, curl, or your frontend to:

  1. Send POST to /authenticate with JSON body:
    
    {
      "username": "admin",
      "password": "password"
    }
            
  2. Copy the returned JWT from the response.
  3. Add it to the Authorization header for future requests:
  4. 
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
          
  5. Send GET request to /hello and check the response.

Handling Token Expiry

JWTs should have a defined expiration time to prevent misuse. This is configured during token creation.

Example (in JwtUtil.java):


.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
    

On the client side, you should always track expiry. If the token is expired, your backend will return:


HTTP 403 Forbidden - Token Expired
    

To handle this, implement refresh tokens or force a re-login.

Implementing Refresh Tokens

A common pattern is to issue both:

  • Access Token (short-lived JWT)
  • Refresh Token (longer-lived, stored securely)

The refresh token is sent to a secure endpoint like /refresh, validated, and a new JWT is issued.

Example endpoint:


@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody RefreshRequest request) {
  String refreshToken = request.getToken();
  // Validate refresh token logic here
  String newJwt = jwtUtil.generateTokenFromRefresh(refreshToken);
  return ResponseEntity.ok(new AuthenticationResponse(newJwt));
}
    

Common Mistakes in JWT Implementation

  • Using plain strings as secret keys (use long random secrets)
  • Not setting an expiration time
  • Not validating the token signature
  • Allowing JWT in URLs (use headers only)
  • Ignoring security for refresh token endpoints

Secure your application by following OWASP recommendations and rotating tokens periodically.

Best Practices

  • Use HS512 or RSA for signing
  • Store tokens in HTTP-only cookies or secure local storage
  • Always use Bearer <token> format in headers
  • Set token lifetime wisely (15-30 mins typical)
  • Enable refresh token flow for longer sessions
  • Use HTTPS to avoid token leakage

Final Summary

JWT authentication in Spring Boot offers a modern, secure, and scalable way to handle user sessions without storing state. You’ve now learned:

  • What JWT is and why it’s useful
  • How to generate and validate JWT tokens
  • How to protect endpoints with custom filters
  • How to implement refresh tokens and handle expiry

With this knowledge, you can secure any REST API and integrate it with modern frontend frameworks like React, Angular, or mobile apps.

Pro Tip: Always keep security updates in check. JWT is powerful, but only when used correctly.

Post a Comment

0 Comments