The Problem with localStorage
Many tutorials store JWT tokens in localStorage or sessionStorage. This is a critical security mistake — any JavaScript on your page (including third-party scripts) can read these values, making you vulnerable to XSS attacks.
The httpOnly Cookie Solution
By storing the JWT in an httpOnly cookie, the browser automatically includes it in every request, but JavaScript can never read it. This is the gold standard for web authentication.
Implementing the JWT Provider
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
public String generateToken(Authentication authentication) {
return Jwts.builder()
.subject(authentication.getName())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000L))
.signWith(getSigningKey())
.compact();
}
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
return Keys.hmacShaKeyFor(keyBytes);
}
}
The Authentication Filter
The filter extracts the JWT from the cookie and sets the security context:
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
String token = Arrays.stream(request.getCookies())
.filter(c -> "jwt".equals(c.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse(null);
if (token != null && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
Conclusion
Using httpOnly cookies with JWT is more secure than localStorage and doesn't sacrifice developer experience. Combined with proper CSRF protection and HTTPS in production, this pattern is battle-tested and production-ready.