JSON Web Tokens (JWT) have become the de facto standard for stateless authentication in modern web applications, APIs, and microservices. They are compact, self-contained, and easy to pass between services. But that same flexibility makes them dangerously easy to misuse. A single configuration mistake can hand an attacker the keys to every account on your platform.
In this guide we break down what a JWT actually is, the most common security mistakes developers make, and a concrete checklist you can apply to your own implementation today. You can follow along and inspect any token using our JWT Decoder, which runs entirely in your browser so your tokens are never sent anywhere.
What is inside a JWT?
A JWT is three Base64URL-encoded segments separated by dots: the header, the payload, and the signature.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 โ header .eyJzdWIiOiIxMjM0IiwibmFtZSI6IkpvaG4ifQ โ payload .SflKxwRJSMeKKF2QT4fwpMeJf36... โ signature
The header declares the signing algorithm. The payload holds the claims (the data). The signature is what proves the token has not been tampered with. The single most important thing to understand is this:
A JWT is encoded, not encrypted. Anyone who intercepts the token can read the payload instantly.
1. Never store sensitive data in the payload
Because the payload is only Base64-encoded, anyone with the token can decode it. Never put passwords, API keys, credit card numbers, or personally identifiable information (PII) inside a JWT. Store only what you need to identify the user and their permissions, such as a user ID and a role.
// Bad โ leaks PII to anyone holding the token
{ "sub": "1234", "ssn": "123-45-6789", "password": "hunter2" }
// Good โ minimal, non-sensitive claims
{ "sub": "1234", "role": "editor", "exp": 1735689600 }2. Use strong signing algorithms โ and pin them
Prefer asymmetric algorithms such as RS256 or ES256 for distributed systems, or a strong symmetric algorithm like HS512 with a high-entropy secret. Two rules matter most:
- Reject the
nonealgorithm.The infamous "alg: none" attack lets an attacker strip the signature entirely. Your verification code must never accept it. - Pin the expected algorithm on the server. Do not trust the algorithm declared in the token header โ an attacker controls it. Explicitly tell your library which algorithm to verify with.
3. Always set a short expiration
JWTs are difficult to revoke once issued, because they are validated by signature alone with no database lookup. Keep the exp claim short โ typically 15 minutes to 1 hour โ and use a separate, revocable refresh token to obtain new access tokens. This limits the window in which a stolen token is useful.
4. Validate every claim you rely on
Signature verification is necessary but not sufficient. A valid signature only proves the token was issued by you โ it says nothing about whether the token is still appropriate to accept. Always check:
exp(expiration) andnbf(not before)iss(issuer) matches your auth serveraud(audience) matches the current API
5. Store tokens safely on the client
Storing JWTs in localStorage exposes them to any XSS vulnerability on your site. For browser apps, prefer an HttpOnly, Secure, SameSite cookie so JavaScript cannot read the token at all. Pair this with CSRF protection.
Quick security checklist
- โ No PII or secrets in the payload
- โ
Algorithm pinned server-side;
nonerejected - โ
Short
exp+ refresh-token rotation - โ
iss,aud,exp,nbfall validated - โ
Tokens stored in
HttpOnlycookies, notlocalStorage
Frequently asked questions
Can a JWT be decoded without the secret?
Yes. The header and payload are only Base64-encoded, so anyone can read them. The secret is only needed to verify or create the signature, not to read the contents.
Are JWTs better than sessions?
Neither is universally better. JWTs shine for stateless, cross-service authentication. Traditional server sessions are simpler to revoke. Many production systems use both: a short-lived JWT access token plus a server-tracked refresh token.
How do I revoke a JWT immediately?
Because JWTs are self-contained, true revocation requires a server-side denylist or very short lifetimes. Most teams keep access tokens short-lived and revoke the refresh token instead.