JSON Web Tokens (JWT) have become the go-to format for access and ID tokens in modern authentication systems — especially when using OAuth 2.0 or OpenID Connect.
Note: This article belongs to Part 4.2: Token Lifecycle & Retry Logic in our Application Security series.
The truth behind those “self-contained” tokens we love to hate
They’re compact, self-contained, and easy to pass around. But with great power comes great confusion.
- What exactly is inside a JWT?
- How are they validated?
- Why do expired or invalid tokens sometimes still “work”?
- And is stateless always the best choice?
In this post, we’re unboxing JWTs to understand how they work, how to validate them properly, and where things tend to go wrong — especially in production environments.
What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token format used for:
- Authentication (ID tokens)
- Authorization (access tokens)
- Information exchange (claims)
A JWT consists of three parts, separated by dots:
<Header>.<Payload>.<Signature>
Example (shortened for clarity):
eyJhbGciOiJIUzI1NiIs... (header)
eyJzdWIiOiIxMjM0NTY3ODkw... (payload)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c (signature)

🔬 JWT Anatomy: Header, Payload & Signature
1. Header
Describes the token type and signing algorithm:
{
"alg": "RS256",
"typ": "JWT"
}
⚠️ Always prefer asymmetric algorithms (RS256) over symmetric (HS256) for access tokens.
2. Payload
Contains claims (info about the user, app, or token). Example:
{
"sub": "user123",
"iss": "https://login.example.com",
"aud": "my-api",
"exp": 1719852000,
"scope": "read:profile write:settings"
}
Common claims:
sub: Subject (user ID)iss: Issueraud: Audienceexp: Expiration (Unix timestamp)iat: Issued atnbf: Not before- Custom claims: scopes, roles, org, etc.
3. Signature
The result of signing the header and payload using a private key (RS256) or secret (HS256). This ensures the token hasn’t been tampered with.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
🔐 JWT Validation Flow
When a backend receives a JWT, it must validate it thoroughly:
- Signature verification
- Using the public key (for RS256)
- Ensures the token is from a trusted source
- Expiration check (
exp)- Reject if token is expired
- Issuer and audience (
iss,aud)- Match expected values
- Other claim validation
nbf,iat,scope, etc.
Spring Security handles this under the hood with JwtDecoder, but it’s critical to understand that validation isn’t optional — it’s your last line of defense.
🧨 Expiry Pitfalls
1. Clock Skew Issues
Different systems may have slightly different clocks. If your server is ahead of the token issuer, tokens may appear invalid even though they’re fresh.
✅ Solution: Use a small grace window (30–60 seconds) for exp, nbf, and iat.
2. Stale Tokens Accepted
Some systems decode JWTs but forget to verify the signature. Anyone can modify the payload and re-encode it — without verification, you’re just trusting user input.
❌ Never trust a decoded JWT unless it’s verified!
3. Misuse as Session Tokens
JWTs are often used for stateless session management — but long-lived tokens without rotation mean no way to revoke access (even after logout).
✅ Use short-lived access tokens and refresh tokens to maintain sessions.
🧰 Working with JWTs in Spring Security
Spring Boot makes handling JWTs easy using spring-boot-starter-oauth2-resource-server.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-idp.com
Behind the scenes:
- It downloads the public key (
.well-known/jwks.json) - Validates tokens automatically
- Maps claims to security context
Custom claim mapping example:
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
var converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new MyCustomRoleMapper());
return converter;
}
Best Practices for JWTs
✅ Always verify the signature using a trusted key
✅ Keep access tokens short-lived (5–15 mins)
✅ Use refresh tokens with rotation for long-lived sessions
✅ Validate exp, iss, aud, nbf, and iat claims
✅ Prefer RS256 or ES256 over HS256
✅ Avoid putting sensitive data in JWT payloads — they’re just base64-encoded, not encrypted
When Not to Use JWTs
JWTs are great, but not always necessary. Avoid them:
- For simple session-based apps (use cookies + session store instead)
- If you need easy revocation (consider opaque tokens)
- If you don’t control your audience list (confused deputy problem)
Conclusion
JWTs are powerful tools — but they’re not magic. When used wisely, they offer performance and portability. When misused, they create invisible vulnerabilities that are hard to trace.
In this post, you’ve seen:
- How JWTs are built and validated
- What makes them expire (and fail)
- And how to avoid common traps in real-world deployments
Check out https://en.wikipedia.org/wiki/JSON_Web_Token Wikipedia link for more information on the current topic.
What’s Next
Up Next in our Application Security Series is:
Part 5.1 👉 PKCE and the Death of Implicit Flow: How to Secure SPAs & Mobile Apps
We’ll explore why PKCE is the preferred approach for browser-based apps — and why implicit flow is fading into history.
Have a Question?
Still confused about signing algorithms? Want help validating tokens in Spring Boot? Curious how refresh tokens pair with JWTs? Drop a comment — happy to chat!