The Day a JWT Bug Cost Us 3 Hours of Sleep
It was 11 PM on a Friday when the support tickets started flooding in. "Can't login." "Session expired immediately." "Getting kicked out constantly." Our authentication was completely broken, and we had no idea why.
Two hours of frantic debugging later, I copied a JWT from a failed request, pasted it into a decoder, and there it was: the exp claim was set to a date in 1970. Someone had passed seconds instead of milliseconds to the timestamp function. The token was expiring 50 years ago.
That's when I truly understood why every developer needs a JWT decoder in their toolkit. Not just for debugging emergencies, but for everyday API development.
What Is a JWT, Really?
A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe way to represent claims between two parties. In plain English: it's a secure way to pass information that can be verified and trusted.
The concept originated from the need to handle authentication in distributed systems. Traditional sessions required server-side storageâevery request needed to hit a database or cache to verify the session. JWTs flip this: they contain everything needed for verification within the token itself.
JWTs have three parts, separated by dots:
- Header: The algorithm used and token type (usually "JWT")
- Payload: The actual data (claims about the user)
- Signature: The verification hash that proves the token wasn't tampered with
Here's the key insight that trips up many developers: the header and payload are only Base64-encoded, not encrypted. Anyone can decode them. The signature is what prevents tamperingâif you change even one character in the payload, the signature won't match.
â ď¸ Security Warning:
Never put sensitive data in JWT payloads (passwords, credit cards, social security numbers). The payload is encoded, not encryptedâanyone with the token can read it. JWTs are for claims ("this user has admin rights"), not secrets.
đ Developer Story #1: The Microservices Migration
Background: A fintech team was migrating from a monolith to microservices. Their old session-based auth required a central Redis cluster for session storage. Every request hit Redis.
The Problem: Every microservice needed to check the session store. Redis became a bottleneckâ99th percentile latency spiked during peak hours. Cache invalidation caused subtle bugs. Scaling meant scaling Redis, which was expensive.
The Solution: Switch to JWT authentication with RS256 (asymmetric signing). Each service received the public key and could verify tokens independently. No central store needed.
Result: Redis load dropped 80%. Auth latency went from 15ms to 2ms. Services became truly independentâyou could deploy them anywhere without worrying about Redis connectivity.
How JWT Authentication Actually Works
Let me walk through the complete flow, because understanding this is essential for debugging:
1. Login Request
User sends credentials (email/password) to your auth server. The server validates against the database, probably checking a hashed password.
2. Token Generation
If credentials are valid, the server creates a JWT. It builds a payload with claims like:
{
"sub": "user-123",
"email": "user@example.com",
"role": "admin",
"iat": 1703980800,
"exp": 1704067200
}
Then it signs this with the secret key (HS256) or private key (RS256).
3. Token Delivery
The JWT is sent back to the client. Best practice: use an httpOnly cookie for the refresh token, and keep the access token in memory (not localStorageâthat's vulnerable to XSS).
4. Authenticated Requests
Client includes the JWT in the Authorization header: Bearer eyJhbGc.... Every request carries this token.
5. Server Verification
The receiving server verifies the signature (using the secret or public key), checks that the token isn't expired, and reads the claims. No database lookup neededâeverything is in the token.
đ Developer Story #2: The OAuth Integration Nightmare
Background: A SaaS company was integrating "Sign in with Google" using OAuth 2.0. They received an ID token (which is a JWT) from Google after successful authentication.
The Problem: Users were randomly getting "Invalid token" errors. Some worked, some didn't. The development team spent days checking their OAuth flow, scopes, and callback URLs.
The Solution: They decoded the failing tokens and discovered the aud (audience) claim didn't match their client ID. Google was returning tokens intended for a different appâtheir staging environment's client ID had leaked into production config.
Result: Fixed a config file. Five days of debugging solved in 30 seconds once they knew to check the audience claim. They now decode every token during debugging as step one.
JWT Algorithms: HS256 vs RS256
This is where security architecture decisions happen. The algorithm you choose has real implications:
HS256 (HMAC with SHA-256)
- Type: Symmetricâsame secret for signing and verifying
- Pros: Faster, simpler, smaller keys
- Cons: Every service that verifies needs the secret
- Best for: Single-service applications, internal APIs
RS256 (RSA Signature with SHA-256)
- Type: Asymmetricâprivate key signs, public key verifies
- Pros: Only auth server needs the private key; public key can be shared freely
- Cons: Slower, larger keys, more complex setup
- Best for: Microservices, third-party integrations, OAuth
đĄ Pro Tip:
If multiple services need to verify tokens, use RS256. Sharing a symmetric secret across services increases your attack surface. With RS256, compromising a verifying service doesn't compromise token generation.
Common JWT Security Vulnerabilities
Over the years, I've seen (and sometimes caused) various JWT-related security issues. Here are the ones that keep showing up:
The "alg: none" Attack
Some JWT libraries accept tokens with "alg": "none", meaning "this token has no signature, trust it anyway." Attackers could forge tokens by setting this algorithm. Modern libraries reject this, but always verify yours does.
Algorithm Confusion
If your server expects RS256 but accepts HS256, an attacker could use your public key (which is, well, public) as the HS256 secret. Some libraries allowed this switch. Always explicitly specify which algorithm your server accepts.
Weak Secrets
For HS256, a weak secret can be brute-forced. Use at least 256 bits of cryptographic randomness. "secret123" is not a secretâit's the first thing attackers try.
Long Expiration Times
JWTs can't be revoked (easily). A token valid for a year means a stolen token is valid for a year. Keep access tokens short-lived (15 minutes to 1 hour) and use refresh tokens for longer sessions.
đ Developer Story #3: The Security Audit Wake-Up Call
Background: A healthcare startup passed their first external security audit, but barely. The auditors flagged their JWT implementation as "high risk."
The Problems: Access tokens expired in 30 days. The HS256 secret was only 20 characters. Tokens were stored in localStorage (XSS vulnerable). The library didn't reject "alg: none".
The Solution: Complete overhaul. Moved to RS256, 15-minute access tokens, httpOnly cookie refresh tokens, and upgraded the JWT library. Implemented token blacklisting for password changes.
Result: Passed the next audit with no findings. More importantly, they now understood why each decision matteredâsecurity wasn't just a checklist, it was architecture.
Debugging JWTs: A Systematic Approach
When authentication fails, here's my debugging checklist:
1. Decode the Token
First, just see what's in it. Use this toolâpaste the token, check the payload. Does it contain what you expect?
2. Check Expiration
Is exp in the future? Is it reasonable? Watch for timezone issues (all JWT timestamps should be UTC) and unit confusion (seconds vs milliseconds).
3. Verify the Algorithm
Does the header's alg match what your server expects? Mismatches cause signature verification failures.
4. Check Audience and Issuer
If your server validates aud and iss, do the values match your configuration? This catches cross-environment token issues.
5. Look for Missing Claims
Does your server require certain claims (sub, roles, etc.)? Are they present? Null or missing values cause subtle authorization bugs.
6. Check Token Size
JWTs with many claims get large. Some servers have header size limits. If your token is over 4KB, you might hit issues with proxies or CDNs.
JWT vs Sessions: An Honest Comparison
JWTs aren't always the right choice. Here's when each approach works best:
| Aspect | JWT | Sessions |
|---|---|---|
| Stateless | â Yes, self-contained | â No, needs session store |
| Revocation | â Must wait for expiry or implement blacklist | â Instant, just delete session |
| Size | Larger (500 bytes - several KB) | Small (~32 bytes session ID) |
| Cross-domain | â Easy with Authorization header | â Cookie domain restrictions |
| Microservices | â No shared state needed | â Need sticky sessions or shared store |
| Mobile apps | â Natural fit | â Cookie management is messy |
Use JWTs for: APIs consumed by mobile apps, microservices architectures, cross-domain authentication, OAuth/OpenID Connect.
Use sessions for: Traditional web apps, situations requiring instant revocation (admin account compromise), simple single-server deployments.
How This Tool Works
When you paste a JWT, the tool:
- Splits the token into three parts at the dots
- Base64URL-decodes the header and payload (handling the URL-safe alphabet)
- Parses the JSON and pretty-prints it with syntax highlighting
- Converts Unix timestamps to human-readable dates in your timezone
- Calculates whether the token is expired based on the
expclaim
Everything happens in your browser using JavaScript. Your tokens are never sent to any server. This is criticalâproduction JWTs often contain sensitive claims and should never be pasted into tools that might log them.
â Best Practice:
Bookmark this tool for debugging. The next time auth breaksâand it willâstart by decoding the token. Nine times out of ten, the answer is visible in the claims.
Final Thoughts
JWTs are everywhere in modern web development. They're in your API authentication. They're in OAuth flows. They're in the ID tokens from social logins. Understanding themâhow they work, what they contain, what can go wrongâis essential for any developer working with web services.
The decoder above is simple by design. It shows you what's in the token, nothing more. But that's often all you need. When authentication breaks at midnight, being able to instantly see what's actually in the tokenâwhat the expiration is, what claims are present, which algorithm is usedâthat's the difference between a quick fix and an all-night debugging session.
Keep this bookmarked. You'll use it more than you expect.




