๐ Contents
JWT Anatomy: Header, Payload, Signature
A JSON Web Token looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Three sections separated by dots. Each section is Base64URL-encoded JSON:
| Section | Contains | Example |
|---|---|---|
| Header (red) | Token type + signing algorithm | {"alg":"HS256","typ":"JWT"} |
| Payload (purple) | Claims โ user data + metadata | {"sub":"123","name":"John","iat":1516239022} |
| Signature (blue) | Cryptographic signature of header+payload | SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
The signature is computed by: HMACSHA256(base64url(header) + "." + base64url(payload), secret). This means anyone can read the header and payload โ they're just Base64, not encrypted. But they can't modify them without invalidating the signature (unless they know the secret).
How to Decode a JWT in Seconds
You have three options:
1. Use a JWT decoder tool (fastest)
Paste your token into the iluv.tools JWT Decoder. It instantly decodes the header and payload, shows expiration time in human-readable format, and highlights whether the token is expired. No data leaves your browser.
2. Browser console
// Decode JWT payload
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.xxx";
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload);
3. Command line
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.xxx" | cut -d'.' -f2 | base64 -d 2>/dev/null | python -m json.tool
What the Payload Actually Contains
JWT claims are the key-value pairs in the payload. They come in three flavors:
| Claim | Meaning | Example |
|---|---|---|
iss (issuer) | Who created the token | "auth.mysite.com" |
sub (subject) | Who the token is about (user ID) | "user_12345" |
aud (audience) | Who the token is meant for | "api.mysite.com" |
exp (expiration) | Unix timestamp when token expires | 1716931200 |
iat (issued at) | Unix timestamp when token was created | 1716927600 |
nbf (not before) | Token invalid before this timestamp | 1716927600 |
jti (JWT ID) | Unique identifier for this token | "a1b2c3d4" |
The exp claim is the most important one for debugging. If your API returns 401 Unauthorized, decode the JWT first. Check exp โ convert the Unix timestamp to a human date. Is it in the past? Token expired. Is it null or missing? The token may never expire (common with misconfigured auth servers).
Common JWT Problems and How to Fix Them
1. "JWT expired" / 401 errors
Decode the token. Check exp. If it's in the past: the token expired and the client needs to refresh it. Most auth servers set exp to 15-60 minutes after iat. If exp is 50 years in the future: the auth server is misconfigured (or this is a test token).
2. "Invalid signature" errors
The token was modified after signing, OR the server is using a different secret than the one that signed it. Common causes: environment variable mismatch (dev secret vs prod secret), secret rotation, or the client accidentally truncated/modified the token string.
3. "Algorithm not supported"
Check the header's alg field. If it says "none", the token is unsigned โ reject it. The "none algorithm" attack is classic: some JWT libraries accepted tokens with {"alg":"none"} as valid without checking the signature. Always verify alg against a whitelist.
4. Audience mismatch
Check aud in the payload. If your API expects "api.mysite.com" but the token says "other-service.com", the token was issued for a different service. This happens in microservice setups where tokens get routed to the wrong service.
JWT Security: What Can Go Wrong
- Token leakage โ JWTs are bearer tokens. Anyone who has the token can use it. Store in httpOnly cookies, not localStorage (XSS can read localStorage). Set short expiration times and use refresh tokens for longevity.
- No revocation mechanism โ JWTs are stateless by design. Once issued, they're valid until they expire. There's no built-in "log out" or "revoke this token." Solutions: token blacklists (stateful, defeats the purpose), short-lived access tokens (5-15 min) + refresh tokens that can be revoked.
- Weak signing secrets โ If using HS256 (HMAC), the secret must be a cryptographically random string of at least 256 bits (32 bytes). "mysecret" or "password123" can be brute-forced in minutes.
- None algorithm attack โ Always validate
algagainst an allowlist. Never accept"none". - Key confusion attack โ If your server accepts both HS256 (symmetric) and RS256 (asymmetric), an attacker can use the RS256 public key as the HS256 secret to forge tokens. Mitigation: use separate validation paths, or only accept one algorithm.