JWT

Implement JWT (JSON Web Token) authentication in Spring Boot — token generation, validation, filter chain integration, and refresh token patterns for production REST APIs.

Last reviewed: January 2025

·Spring Boot 2.7Spring Boot 3.x
✅
Quick Understanding: JWT is a self-contained token that encodes user information. The server signs it with a secret key; the client sends it in every request. The server validates the signature — no database lookup needed per request. This makes JWT perfect for stateless microservices. A JWT has three parts: header.payload.signature, all Base64-encoded.

🔍 What Is It?

JWT (JSON Web Token) is a compact, URL-safe token format for transmitting claims between parties.

JWT structure:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.  <- Header (algorithm)
eyJzdWIiOiJ1c2VyQGV4YW1wbGUuY29tIiwi...  <- Payload (claims)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQ  <- Signature

Standard claims:

  • →sub — subject (user ID or username)
  • →iat — issued at
  • →exp — expiration time
  • →iss — issuer
  • →aud — audience

Custom claims:

  • →roles — user roles
  • →userId — database ID
  • →tenantId — for multi-tenant apps

JWT flow:

  1. Login → server validates credentials, returns JWT
  2. Client stores JWT (memory or HttpOnly cookie)
  3. Client sends Authorization: Bearer <token> on every request
  4. Server validates signature + expiry — no DB lookup needed
  5. Server extracts claims and processes request

🎯 Why Do We Need It?

JWT enables stateless authentication:

  • →No server-side session storage needed
  • →Scales horizontally — any server can validate any token
  • →Works across microservices — pass the JWT between services
  • →Self-contained — user info is in the token, no DB lookup per request

Alternative: sessions (stored in server memory or Redis). Sessions require session storage; JWT doesn't. For microservices, JWT is the standard.

🏗️ What Happens in Real Projects?

In enterprise microservices:

  • →Access token: 15-60 minute expiry, sent in Authorization header
  • →Refresh token: 30-day expiry, stored in HttpOnly cookie or secure storage
  • →Token contains: userId, email, roles, tenantId
  • →API Gateway validates JWT before forwarding to downstream services
  • →Downstream services trust the JWT (already validated by gateway)

⚙️ How It Works

JWT Filter:

A OncePerRequestFilter runs before every request:

  1. Reads Authorization header
  2. Extracts the token (after "Bearer ")
  3. Validates signature with the secret key
  4. Checks expiration
  5. Extracts username from claims
  6. Loads UserDetails (or creates from claims)
  7. Sets Authentication in SecurityContext
  8. Request proceeds to controller

💻 Example

Complete JWT Implementation — JwtService, JWT filter, and integration with Spring Security.

java
@Service
public class JwtService {

    private final SecretKey signingKey;
    private final long accessTokenExpiryMs;
    private final long refreshTokenExpiryMs;

    public JwtService(
            @Value("${app.jwt.secret}") String secret,
            @Value("${app.jwt.access-token-expiry-ms:900000}") long accessExpiry,
            @Value("${app.jwt.refresh-token-expiry-ms:2592000000}") long refreshExpiry) {
        this.signingKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        this.accessTokenExpiryMs = accessExpiry;
        this.refreshTokenExpiryMs = refreshExpiry;
    }

    public String generateAccessToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList()));
        claims.put("type", "access");
        return buildToken(claims, userDetails.getUsername(), accessTokenExpiryMs);
    }

    public String generateRefreshToken(UserDetails userDetails) {
        Map<String, Object> claims = Map.of("type", "refresh");
        return buildToken(claims, userDetails.getUsername(), refreshTokenExpiryMs);
    }

    private String buildToken(Map<String, Object> claims,
                               String subject, long expiryMs) {
        return Jwts.builder()
            .claims(claims)
            .subject(subject)
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expiryMs))
            .signWith(signingKey, Jwts.SIG.HS256)
            .compact();
    }

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

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

    private boolean isTokenExpired(String token) {
        return extractClaim(token, Claims::getExpiration).before(new Date());
    }

    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        Claims claims = Jwts.parser()
            .verifyWith(signingKey)
            .build()
            .parseSignedClaims(token)
            .getPayload();
        return claimsResolver.apply(claims);
    }
}

🤔 Common Confusions

❓ Where should I store the JWT on the client?

HttpOnly cookies are the most secure — JavaScript can't read them, preventing XSS attacks. localStorage is vulnerable to XSS. Memory (JavaScript variable) is safest but lost on page refresh. For web apps: HttpOnly cookie for refresh token, memory for access token. For mobile apps: secure storage (iOS Keychain, Android Keystore).

❓ How do I invalidate a JWT before it expires?

JWTs are stateless — you can't invalidate them without state. Solutions: (1) Short expiry (15 min) + refresh tokens. (2) Token blacklist in Redis (check on every request). (3) Token version in database (increment on logout, validate version in JWT). Option 2 is most common for logout functionality.

❓ HS256 vs RS256 — which signing algorithm to use?

HS256 (HMAC-SHA256) uses a shared secret — simple, fast, good for single-service. RS256 (RSA-SHA256) uses a public/private key pair — the private key signs, the public key verifies. Use RS256 for microservices where multiple services need to verify tokens without sharing a secret. Identity providers (Keycloak, Auth0) use RS256.

🚨 Common Production Issues

JWT secret too short or predictable — use at least 256-bit (32 char) random secret

Sensitive data in JWT payload — JWT is Base64-encoded, not encrypted; anyone can decode the payload

No token expiry — always set exp claim; never create tokens without expiration

JWT secret hardcoded in application.properties committed to Git

Not validating JWT on every request — the filter must run for every authenticated endpoint

✅ Best Practices

Use short access token expiry (15-60 minutes) with refresh tokens

Store JWT secret in environment variables or a secrets manager, never in code

Use RS256 for microservices — share public key, keep private key in auth service only

Never put sensitive data (SSN, credit card) in JWT payload — it's not encrypted

Implement token blacklisting in Redis for logout functionality

Add the jti (JWT ID) claim for token tracking and revocation

🏛️ Architect's Note

Senior Architect's Perspective

In a microservices architecture, the API Gateway validates JWTs. Downstream services trust the gateway and don't re-validate. The gateway can also enrich the request with user info (X-User-Id header) so services don't need to parse the JWT. This pattern reduces latency (one validation per request, not per service) and centralizes security.

💬 Frequently Asked Questions

What library should I use for JWT in Spring Boot?

JJWT (io.jsonwebtoken:jjwt-api) is the most popular. Spring Security 6 also has built-in OAuth2 Resource Server support that handles JWT validation automatically — configure spring.security.oauth2.resourceserver.jwt.secret-key and Spring validates JWTs without custom filter code.

🎯 Key Takeaways

1JWT is a self-contained, signed token — no DB lookup needed for validation
2Structure: header.payload.signature, all Base64-encoded
3OncePerRequestFilter validates JWT and sets SecurityContext on every request
4Access tokens: short expiry (15-60 min); refresh tokens: long expiry (30 days)
5Never put sensitive data in JWT payload — it is not encrypted
6Store JWT secret in environment variables, never in source code

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Spring Boot learning path

Browse the full Spring Boot topic collection and learning order on SplashRide.

Spring Boot Interview Prep

Practice Spring Boot interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Spring Security

Master Spring Security — the comprehensive security framework for authentication, authorization, and protection against common web vulnerabilities in Spring Boot applications.

Authentication

Implement authentication in Spring Boot — UserDetailsService, AuthenticationManager, login endpoints, and the complete authentication flow for enterprise REST APIs.