JWT Authentication Explained: Internals, Common Pitfalls, and Secure Spring Boot Implementation

JWT Authentication Explained: Internals, Common Pitfalls, and Secure Spring Boot Implementation thumbnail

JSON Web Tokens (JWT) have become the de facto standard for stateless authentication in modern APIs. They are widely used in OAuth2, microservices, mobile apps, and single-page applications. Yet, JWTs are also frequently misunderstood and misused - leading to serious security vulnerabilities.

This article starts from the basics, dives into JWT internals, highlights lesser-known JWT behaviors, and demonstrates a secure JWT implementation using Spring Boot and Spring Security. You can try this browser-based JWT playground to visualize and validate JWT tokens.


What Is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe token format used to represent claims between two parties. Unlike traditional session-based authentication, JWTs are self-contained - the token itself carries all the information required for authentication and authorization.

JWTs are commonly used for:

  • API authentication
  • Single Sign-On (SSO)
  • Microservice-to-microservice communication
  • Mobile and SPA authentication

JWT Structure (What's Really Inside)

A JWT consists of three Base64URL encoded parts separated by dots:

header.payload.signature

1. Header

The header defines metadata about the token - most importantly the signing algorithm.


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

Lesser-known fact: The typ field is optional and often ignored by libraries. Security decisions should never depend on it.

2. Payload (Claims)

The payload contains claims - statements about the user or token.


{
  "sub": "user_123",
  "iss": "https://devglan.com",
  "aud": "devglan-api",
  "iat": 1710000000,
  "nbf": 1710000000,
  "exp": 1710003600,
  "role": "developer"
}
  

Important: JWT payloads are encoded, not encrypted. Anyone with the token can read the payload. Never store secrets, passwords, or PII inside JWTs.

3. Signature

The signature ensures token integrity and authenticity. It is computed using:


Base64UrlEncode(header) + "." + Base64UrlEncode(payload)
  

and signed using a secret (HMAC) or private key (RSA/ECDSA).


Why JWT Validation Is More Than Just Signature Checking

Many developers assume that a valid signature means a valid token. This is one of the most common JWT misconceptions. If you are using any library such as jjwt from io.jsonwebtoken then it will take care of the validation internally.

A secure JWT validation must include:

  • Signature verification
  • exp (expiration) validation
  • nbf (not before) validation
  • iss (issuer) validation
  • aud (audience) validation

Skipping any of these checks can result in token replay or privilege escalation.


Common JWT Mistakes (Seen in Real Systems)

1. Trusting the Algorithm from the Token

Applications should never trust the alg value provided by the token. Always enforce the expected algorithm server-side.

2. Using Long-Lived Tokens

JWTs are often issued with expiration times of days or weeks. This increases blast radius if a token is leaked.

Best practice: Short-lived access tokens + refresh tokens.

3. Skipping Audience Validation

Without aud validation, a token issued for one service may be accepted by another.

4. Putting Authorization Logic in the Client

JWTs can carry roles, but enforcement must always happen server-side. Client-side role checks are purely cosmetic.

5. Treating JWT as Encrypted Data

JWT is not encryption. Anyone can decode it. Use JWE (encrypted JWT) if confidentiality is required.


Spring Boot JWT Authentication (Secure Setup)

Setting up jwt in a spring boot project is simpler with the use of below maven dependency.

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

JWT Utility Class

The JwtUtil class acts as a centralized helper responsible for generating, signing, and validating JSON Web Tokens (JWT) within the application. It encapsulates all JWT-related logic, ensuring that security concerns remain isolated from controllers, filters, and business logic.

  • The utility uses an HMAC SHA-256 (HS256) secret key to sign and verify tokens, guaranteeing token integrity and preventing tampering.
  • The generateToken() method creates a signed JWT containing standard claims such as:
    • sub (subject / username)
    • iss (issuer)
    • iat (issued at)
    • exp (expiration time)
  • The validate() method verifies the token signature, enforces the expected issuer, and safely parses the claims. Any token that is expired, malformed, or tampered with will fail validation.
  • The getUsernameFromToken() method provides a convenient way to extract the authenticated user's identity from the JWT.
  • The getAllClaimsFromToken() method exposes the full set of token claims, which can be useful for authorization checks, logging, or auditing purposes.

By isolating JWT creation and validation logic in a dedicated utility class, the application ensures consistent token handling, improved maintainability, and a reduced risk of security misconfigurations.

@Component
public class JwtUtil {

  private final Key key =
      Keys.hmacShaKeyFor("very-secret-key-change-me".getBytes());

  public String generateToken(String username) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuer("https://devglan.com")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 3600000))
        .signWith(key, SignatureAlgorithm.HS256)
        .compact();
  }

  public Claims validate(String token) {
    return Jwts.parserBuilder()
        .requireIssuer("https://devglan.com")
        .setSigningKey(key)
        .build()
        .parseClaimsJws(token)
        .getBody();
  }

  public String getUsernameFromToken(String token) {
	return getClaimFromToken(token, Claims::getSubject);
  }
	
	
  public Claims getAllClaimsFromToken(String token) {
	return Jwts.parser().verifyWith(getSecretKey()).build().parseSignedClaims(token).getPayload();
  }

}

JWT Filter

public class JwtFilter extends OncePerRequestFilter {

  private JwtUtil jwtUtil;

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

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

    if (auth != null && auth.startsWith("Bearer ")) {
      String token = auth.substring(7);
      Claims claims = jwtUtil.validate(token);

      UsernamePasswordAuthenticationToken authentication =
          new UsernamePasswordAuthenticationToken(
              claims.getSubject(), null, List.of());

      SecurityContextHolder.getContext()
          .setAuthentication(authentication);
    }

    chain.doFilter(request, response);
  }
}

Adding Custom Roles in JWT Token

Additionally you can add user roles as a SET in the JWT claims as below so that you do not have to do a round trip to DB while validating tokens.

Map<String, Set<String>> claims = new HashMap<>();
claims.put("scopes", roles);
return Jwts.builder()
        .setSubject(username)
        .claims(claims)
        .setIssuer("https://devglan.com")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 3600000))
        .signWith(key, SignatureAlgorithm.HS256)
        .compact();

Later you can extract the custom claims in JwtFilter.java

Set<String>roles = (Set<String>) jwtTokenUtil.getAllClaimsFromToken(authToken).get("scopes");

Final Thoughts

JWTs are powerful, but they are easy to misuse. Understanding what happens beyond encoding and decoding is crucial for building secure systems.

Treat JWTs as signed messages - not sessions, not encrypted blobs - and always validate more than just the signature.

Support This Free Tool!

Buying me a coffee helps keep the project running and supports new features.

cards
Powered by paypal

Thank you for helping this blog thrive!

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Spring Security