JWT explained: what actually goes inside that token
Every login flow you touch these days involves JWTs, but 'JSON Web Token' hides a lot. Here's the three-part structure, the security pitfalls, and when not to use them.
The three parts
A JWT is three base64url-encoded strings joined by dots:
xxxxx.yyyyy.zzzzz
| | |
header payload signature
Base64url decode the first part and you get the header โ a small JSON object saying which signing algorithm was used ({"alg":"HS256","typ":"JWT"}).
Decode the second part and you get the payload โ another JSON object with the claims (who the token is about, when it expires, what it's for). This is the interesting part.
The third part is the signature โ bytes proving the token was issued by someone with the right secret or private key. You can't decode it into JSON.
Everything before the signature is plaintext once you base64-decode it. This is the #1 misconception about JWTs โ they are signed, not encrypted. Anyone with the token can read the claims. That's why you should never put a password, credit card number, or PII in a JWT payload.
The standard claims
A well-formed JWT payload usually contains some of these:
iss(issuer): who created the token."https://auth.example.com".sub(subject): who the token is about, usually a user ID.aud(audience): who the token is for. Reject tokens meant for a different service.exp(expiration): Unix timestamp after which the token is invalid.iat(issued-at): when the token was created.nbf(not-before): the token isn't valid before this time.jti(JWT ID): a unique identifier for the token โ useful for revocation.
Beyond these, apps add custom claims: roles, permissions, tenant ID, feature flags.
JWT Decoder formats these with human-readable dates so you don't have to squint at Unix timestamps.
The security pitfalls
Three that bite people repeatedly:
1. The alg: none attack. Some early JWT libraries accepted tokens signed with the "none" algorithm โ meaning no signature at all. Attackers would forge tokens with {"alg":"none"}, remove the signature, and libraries would accept them. Any modern library rejects this, but if you're implementing verification yourself: always check the algorithm matches what you expect.
2. Confusion between HS256 and RS256. HS256 uses a shared secret; RS256 uses a public/private key pair. If your verification code passes the public key as a "secret" to an HS256 verifier, an attacker can sign valid tokens using that same public key. The fix: hard-code the expected algorithm rather than reading it from the token header.
3. Long-lived tokens with no revocation. JWTs are stateless โ the server doesn't remember what it issued. If a token leaks, it stays valid until it expires. For anything sensitive, keep expirations short (5-15 min for access tokens) and rely on a rotating refresh-token flow.
When JWTs are the wrong choice
JWTs excel at stateless, cross-service auth โ "I need service B to trust a claim service A made about a user." That covers most microservice architectures.
They are the wrong choice when:
- You need instant revocation on logout, password change, or account ban. Because JWTs are stateless, the only ways to invalidate one before expiry are (a) keep a revocation list, defeating the point of statelessness, or (b) rotate the signing key, invalidating everyone.
- You have a single trusted server. Traditional session cookies are simpler, safer (auto revocable, easier CSRF handling), and don't leak claims to anyone who intercepts them.
- You want to store large amounts of user data. Every request re-transmits the whole token. Keep payloads under ~1 KB.
Decoding without leaking
The instinct when you're handed a JWT to debug is to paste it into the first "JWT debugger" that shows up in Google search. Don't. Production tokens carry claims about real users; the site that decodes it can log them.
JWT Decoder runs entirely in your browser. Nothing is uploaded, logged, or transmitted โ the base64url decoding happens locally via native browser APIs. That's the only version of JWT decoding that makes sense for real tokens.
The debugging workflow
You'll usually reach for JWT tools in this order:
- Decode the token to see what's inside โ JWT Decoder.
- Format the payload if it contains nested JSON โ JSON Formatter.
- URL-decode the token if you pulled it out of a query string โ URL Encoder / Decoder.
All three run locally, so a full auth-debugging session doesn't expose the token to any third party.