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:

SectionContainsExample
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+payloadSflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_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).

โš ๏ธ Critical misunderstanding: JWTs are signed, not encrypted. Anyone who has the token can decode and read the payload. Never put secrets (passwords, API keys, PII) in JWT claims. Use JWE (JSON Web Encryption) if you need confidentiality.

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:

ClaimMeaningExample
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 expires1716931200
iat (issued at)Unix timestamp when token was created1716927600
nbf (not before)Token invalid before this timestamp1716927600
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 alg against 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.
๐Ÿ’ก Debugging workflow: When authentication breaks, decode the JWT first. It answers 80% of questions instantly: Is it expired? Is the user ID correct? Are the scopes/roles what you expect? A JWT decoder is the first tool, not the last.