Home/Learn/Spring Boot/JWT Authentication

JWT Authentication

Intermediate
Security

JWT is a stateless authentication token — the server signs it, clients store and send it. A OncePerRequestFilter validates the token on every request and populates the SecurityContext without any database lookup.

Overview

A JWT has three parts: Header (algorithm), Payload (claims: sub, iat, exp, custom), Signature (HMAC or RSA). The server signs the token with a secret key; clients include it in the Authorization: Bearer header. A servlet filter (OncePerRequestFilter) intercepts each request, validates the token signature and expiry, and sets the Authentication in SecurityContextHolder. No session, no database lookup — purely stateless.

JWT Service — Generate and Validate Tokens

Use the JJWT library for JWT creation and parsing. Sign with HMAC-SHA256 using a secret key of at least 256 bits. Store the secret in an environment variable, never in code.

Java — JwtService with JJWT 0.12
<!-- pom.xml -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
</dependency>

@Service
public class JwtService {

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

    @Value("${app.jwt.expiry-minutes:60}")
    private int expiryMinutes;

    private SecretKey signingKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    public String generateToken(String userId) {
        return Jwts.builder()
            .subject(userId)
            .issuedAt(new Date())
            .expiration(Date.from(Instant.now().plusSeconds(expiryMinutes * 60L)))
            .signWith(signingKey())
            .compact();
    }

    public String extractUserId(String token) {
        return parseClaims(token).getSubject();
    }

    public boolean isValid(String token) {
        try {
            parseClaims(token); // throws on invalid/expired
            return true;
        } catch (JwtException e) {
            return false;
        }
    }

    private Claims parseClaims(String token) {
        return Jwts.parser()
            .verifyWith(signingKey())
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
}

JWT Filter — Authenticate Every Request

OncePerRequestFilter guarantees exactly one execution per request. It extracts the token from the Authorization header, validates it, and sets Authentication in SecurityContextHolder so downstream code knows the user is authenticated.

Java — OncePerRequestFilter for JWT validation
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JwtService jwtService;

    public JwtAuthFilter(JwtService jwtService) {
        this.jwtService = jwtService;
    }

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

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

        // No token — pass through (let security rules handle it)
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        String token = header.substring(7); // strip "Bearer "

        if (jwtService.isValid(token)
                && SecurityContextHolder.getContext().getAuthentication() == null) {

            String userId = jwtService.extractUserId(token);

            // Set authentication — no DB lookup needed
            UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(
                    userId,          // principal — available via @AuthenticationPrincipal
                    null,            // credentials — not needed post-auth
                    List.of(new SimpleGrantedAuthority("ROLE_USER"))
                );
            auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        chain.doFilter(request, response);
    }
}

// Register in SecurityConfig
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)

Key Points to Remember

  • 1JWT = Header.Payload.Signature — the server signs with a secret key; clients cannot forge tokens.
  • 2JWTs are stateless — no database lookup required for validation, just signature verification.
  • 3OncePerRequestFilter ensures the JWT filter runs exactly once per request, not once per servlet dispatch.
  • 4Set Authentication in SecurityContextHolder after validation — this is how Spring Security knows the user is logged in.
  • 5Use a secret key of at least 256 bits for HMAC-SHA256 signing — generate with Keys.secretKeyFor(SignatureAlgorithm.HS256).
  • 6Short expiry (15–60 min) + refresh tokens is more secure than long-lived JWTs — invalidation is impossible without a blocklist.

Interview Questions

Sign in to ask Aria
1

What are the three parts of a JWT and what does each contain?

EasyInfosys
2

How does the server validate a JWT without hitting the database?

EasyTCS
3

How do you invalidate a JWT before it expires?

HardAmazon
4

Why use OncePerRequestFilter instead of implementing Filter directly?

MediumThoughtWorks
5

What is the risk of storing a JWT in localStorage vs HttpOnly cookie?

MediumRazorpay

Ask Aria about JWT Authentication

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…