How Base64URL Decoding Reveals the Token's Secrets
JWTs aren't encrypted — they're encoded. The encoding scheme is Base64URL, a variant of Base64 that's safe for URLs. This means decoding a JWT requires nothing more than splitting the string at each dot and running a standard decode function. Take a token like eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0MjMsImV4cCI6MTcwNDY3MjAwMH0.signature — that first chunk decodes to {"alg":"HS256","typ":"JWT"}.
The payload section follows the same pattern. That middle chunk eyJ1c2VyX2lkIjo0MjMsImV4cCI6MTcwNDY3MjAwMH0 decodes to {"user_id":423,"exp":1704672000}. The exp value is a Unix timestamp — 1704672000 represents January 8, 2024, at midnight UTC. Your browser's JavaScript handles this conversion automatically, but the math is straightforward: Unix timestamps count seconds since January 1, 1970.
The third section — the signature — decodes to binary data that looks like gibberish. That's intentional. The signature is created by hashing the header and payload together with a secret key, and it can only be verified by someone who possesses that same key. No client-side tool can validate it without access to the server's secret.
Debugging a Failed Login: Walking Through a Real Token Inspection
Imagine you're troubleshooting why users keep getting logged out of your application. They complain it happens exactly 15 minutes after logging in, every single time. You grab a token from browser storage and paste it into this decoder. The header shows HS256 algorithm — nothing unusual there. But the payload reveals the problem: exp is set to 1704672900, which translates to 15 minutes after the token was issued.
The security checker flags this immediately. While 15-minute expiration is actually recommended for access tokens, the tool also notices there's no refresh token mechanism apparent in the claims. Your users are getting a short-lived token with no way to renew it silently. The fix becomes obvious: implement a refresh token flow that issues new access tokens before the old ones expire.
You also notice the payload contains role: "admin" in plain text. That's not inherently dangerous since the signature prevents tampering, but it does tell anyone inspecting the token exactly what privileges this user has. Consider whether such detailed role information needs to live in the token at all.
Beyond Basic Debugging: Security Audits and Algorithm Migration
Security teams use JWT decoders to audit tokens across their entire application fleet. Paste 50 different tokens from various services, and you'll quickly spot inconsistencies — one service using HS256 while others use RS256, some tokens expiring in 24 hours while others last 7 days. These discrepancies often indicate technical debt or forgotten legacy code that hasn't been updated to current security standards.
Algorithm migration is another practical use case. If you're moving from HS256 to RS256 for better security in a microservices environment, you need to verify that new tokens actually carry the correct algorithm header. The tool instantly confirms whether your auth server is issuing RS256 tokens as expected, without requiring you to dig through server logs or write test code.
Mobile developers find particular value during debugging. When your Flutter app receives a 401 error, pasting the stored token reveals whether the problem is an expired token, a missing claim your API requires, or something else entirely. It's faster than adding print statements throughout your authentication code.
Three JWT Mistakes That Create Real Security Holes
The most common mistake is trusting a JWT without verifying its signature on the server. Attackers can decode a token, modify the payload to change their user_id from 423 to 1, re-encode it, and send it back. Without signature verification, your server accepts this tampered token as legitimate. Always verify signatures server-side using your authentication library — never rely on the token's contents alone.
Setting algorithm to "none" is another critical error. Some JWT libraries accept tokens with no signature at all if the header specifies alg: none. Attackers exploit this by stripping the signature and changing the algorithm claim. The security checker warns you if it detects this configuration. Your server should explicitly reject tokens that don't use your expected algorithm.
Finally, storing sensitive data in JWT payloads creates unnecessary risk. Social Security numbers, passwords, API keys — none of these belong in a token. Remember that JWTs are encoded, not encrypted. Anyone who intercepts the token can read everything inside it. Keep payloads minimal: user ID, essential roles, expiration time. Store sensitive details server-side and reference them by ID.
The 7 Most Common JWT Security Mistakes
JWT is a powerful authentication mechanism — but it's frequently misimplemented. Here are the most common security mistakes:
- No expiration time (exp): Tokens without
expare valid forever. A stolen token grants permanent access. Always set short lifetimes (15–60 minutes for access tokens). - Using the `none` algorithm: Some early JWT libraries accepted
alg: none, meaning no signature. Never accept unsigned tokens in production. - Storing JWTs in localStorage: Accessible to any JavaScript on the page, including XSS payloads. Prefer
httpOnlycookies. - Not validating the `iss` (issuer) claim: Allows tokens from any issuer to be accepted.
- Symmetric keys that are too short: For HS256, use a secret of at least 256 bits (32 bytes).
- Not rotating secrets: If your signing secret leaks, all tokens signed with it are compromised.
- Trusting the `kid` (key ID) without validation: Advanced but critical — always whitelist valid key IDs.
Access Tokens vs Refresh Tokens: The Right Architecture
A common pattern for JWT-based authentication uses two token types:
Access Token (short-lived, 15 min):
- Sent with every API request in Authorization: Bearer <token>
- Stateless — no database lookup needed to verify
- If stolen, attacker has limited window before it expires
Refresh Token (long-lived, 7–30 days):
- Stored securely (httpOnly cookie or server-side session)
- Used only to obtain a new access token
- Can be revoked by deleting it server-side
This separation is the industry best practice. It gives you the performance of stateless auth (no DB lookup per request) with the security of revocable sessions.
Never store sensitive user data in the JWT payload — it's only Base64-encoded, not encrypted. Anyone who intercepts it can read it.
Algorithm Selection: HS256 vs RS256 vs ES256
Choosing the right signing algorithm matters:
- HS256 (HMAC-SHA256): Fast, simple. Single shared secret. Good for monolithic apps where the same service issues and verifies tokens. Risk: if any service that can verify tokens is compromised, attacker can forge tokens.
- RS256 (RSA-SHA256): Asymmetric. Private key signs, public key verifies. Ideal for microservices — each service can verify without access to the signing key. Slower than HS256.
- ES256 (ECDSA with P-256): Like RS256 but with smaller, faster keys. Modern choice for APIs with high throughput requirements.
Recommendation: For new projects, use RS256 or ES256. The performance overhead is minimal compared to the security benefit.