What Is a JWT? JSON Web Tokens Explained, With the Security Mistakes to Avoid
What is a JWT? How JSON Web Tokens are structured, what the claims mean, how signing works, and the security mistakes (alg none, weak keys, storage) to avoid.
A JWT (JSON Web Token) is a compact, signed piece of JSON that a server gives a client to prove who they are or what they're allowed to do. It has three parts, a header, a payload of "claims" and a signature, joined by dots. The signature stops tampering, but anyone can read the contents.
JWTs sit behind the logins of countless web apps, mobile apps and APIs. They are also easy to get wrong in ways that turn a login system into an open door. This guide explains what a JWT is, how it works, and the security mistakes that come up again and again in small business apps and SaaS products.
To look inside any token as you read, paste it into Make It Flow's free JWT decoder. It decodes locally in your browser, so the token isn't sent anywhere.
Key takeaways
- A JWT is three Base64url parts (header, payload, signature) joined by dots, and the payload is readable by anyone.
- The signature proves the token hasn't been changed; it doesn't hide the contents, so never put secrets or sensitive personal data in a JWT.
- Always fix the accepted algorithm on the server and reject alg none, as RFC 8725 recommends.
- Keep access tokens short-lived, validate exp, iss and aud, and store browser tokens where JavaScript can't read them where possible.
What is a JWT made of?
JWTs are defined in RFC 7519. A typical token looks like this, with line breaks added for readability:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkphbmUiLCJleHAiOjE3OTk5OTk5OTl9
.4Hb8n2k...signature...
Each section is Base64url-encoded JSON (or raw bytes, for the signature):
| Part | Contains | Example (decoded) |
|---|---|---|
| Header | Token type and signing algorithm | {"alg":"HS256","typ":"JWT"} |
| Payload | Claims about the user and the token | {"sub":"1234","name":"Jane","exp":1799999999} |
| Signature | Cryptographic proof the first two parts haven't changed | Binary, Base64url-encoded |
The signature is calculated over header.payload using a key. If anyone changes one character of the header or payload, the signature no longer matches and a properly configured server rejects the token.
The payload is not secret
Base64url is an encoding, not encryption. Anyone holding the token, whether a browser extension, a proxy log or an attacker, can read the payload. A signed JWT (technically a JWS, defined in RFC 7515) protects integrity, not confidentiality.
So don't put passwords, API keys, card details, health data or anything else sensitive in a JWT payload. If you genuinely need encrypted tokens, the standard is JWE (RFC 7516), but most apps are better off keeping sensitive data on the server.
JWT claims explained
Claims are the key-value pairs in the payload. RFC 7519 registers seven standard ones. All are optional, but several should be treated as essential:
| Claim | Name | What it means |
|---|---|---|
iss |
Issuer | Who created the token, such as your auth server |
sub |
Subject | Who the token is about, usually a user ID |
aud |
Audience | Who the token is intended for, such as your API |
exp |
Expiration time | After this moment, the token must be rejected |
nbf |
Not before | The token must not be accepted before this moment |
iat |
Issued at | When the token was created |
jti |
JWT ID | A unique ID, useful for revocation or replay checks |
exp, nbf and iat are NumericDate values, which means seconds since the Unix epoch, not milliseconds. Mixing the two is a classic bug: a token that should expire in an hour suddenly lasts for tens of thousands of years. Our guide to the Unix timestamp explains the difference, and the timestamp converter turns an exp value into a readable date.
You can also add your own private claims, such as role or tenant_id. Keep them minimal, because every claim travels with every request.
How JWT signing algorithms work
The alg header says how the signature was made. The main families are:
| Algorithm | Type | Key setup | Typical use |
|---|---|---|---|
| HS256 / HS384 / HS512 | HMAC (symmetric) | One shared secret signs and verifies | A single app that issues and checks its own tokens |
| RS256 / PS256 | RSA (asymmetric) | Private key signs, public key verifies | Identity providers, many services verifying one issuer |
| ES256 | ECDSA (asymmetric) | Private key signs, public key verifies | As RSA, with smaller keys and signatures |
| EdDSA | Edwards-curve (asymmetric) | Private key signs, public key verifies | Newer systems where libraries support it |
none |
No signature | None | Should almost never be accepted |
Symmetric (HS256) is simple, but every service that verifies tokens also holds the secret that can create them. Asymmetric (RS256, ES256) lets you publish the public key, often as a JWKS (JSON Web Key Set) endpoint, so other services can verify tokens without being able to forge them.
Common JWT security mistakes
The IETF published RFC 8725, JSON Web Token Best Current Practices, specifically because real-world JWT implementations kept failing in the same ways. These are the ones to check first.
1. Accepting alg: none
The JWT standards include an "unsecured" mode where alg is none and the signature is empty. Some libraries used to honour whatever the token's header said. An attacker could take a valid token, change the payload to "role":"admin", set alg to none, delete the signature, and walk in.
RFC 8725 says libraries should not consume none tokens unless the caller explicitly asks for them. In your code, never let the token choose its own algorithm. Configure the exact algorithm or algorithms you accept and reject everything else.
2. Algorithm confusion (RS256 to HS256)
RFC 8725 section 2.1 describes this attack. A server expects RS256 and verifies with its RSA public key. The attacker changes the header to HS256 and signs the token using that public key, which is public, as the HMAC secret. A careless library then "verifies" the HMAC with the same public key and accepts the forgery.
The fix is the same as above: pin the algorithm, and bind each key to exactly one algorithm.
3. Weak HMAC secrets
An HS256 token signed with secret, password123 or your company name can be brute-forced offline. The attacker only needs one captured token. RFC 8725 section 3.5 says human-memorable passwords must not be used directly as HMAC keys. Use a long, randomly generated key; our password generator can produce one. Keep it out of your source code and Git history.
4. Not validating the claims
A valid signature only proves the issuer created the token. You still need to check that:
expis in the future (allow a small clock skew of a minute or two at most)nbf, if present, is in the pastissis the issuer you trustaudmatches your service, so a token issued for one app can't be replayed against another- the user or tenant in
substill exists and is still allowed access
5. Tokens that never expire
A JWT is usually valid until exp, and you can't easily "log it out", because the server doesn't keep a record of it. That's the trade-off for being stateless. Long-lived access tokens turn any leak into a long-lived breach.
Common practice is a short-lived access token, minutes rather than days, paired with a longer-lived refresh token that is stored more carefully, can be rotated, and can be revoked on the server. If you need instant revocation, keep a deny-list of jti values or check a session version on each request.
6. Putting sensitive data in the payload
As covered above, the payload is readable. Tokens often end up carrying email addresses, phone numbers and internal permissions lists. Anything personal in a JWT counts as personal data under laws such as the GDPR, UK GDPR and California's CCPA, and it gets copied into logs, analytics and browser storage. Keep the payload to an ID and the minimum you need.
7. Trusting the jku, x5u or kid headers blindly
These headers tell the verifier where to find the key. If your code fetches a key from whatever URL the token supplies, an attacker can host their own key and sign whatever they like. Only load keys from a fixed, trusted location, and treat kid as a lookup value, never as a file path or database query.
Where should you store a JWT in the browser?
There's no perfect answer, only trade-offs:
| Storage | Readable by JavaScript? | Main risk | Notes |
|---|---|---|---|
localStorage / sessionStorage |
Yes | Any XSS can steal the token | Simple, but one injected script leaks every token |
| In-memory variable | Yes, but only while the page is open | XSS can still use it during the session | Lost on refresh, so it's usually paired with a refresh cookie |
HttpOnly, Secure, SameSite cookie |
No | CSRF, if SameSite and CSRF protection aren't set up |
Generally the safer default for browser apps |
The OWASP Session Management Cheat Sheet advises against keeping tokens or session IDs in web storage, because any script on the page can read them, and recommends HttpOnly, Secure, SameSite cookies instead. For most web apps, that means an HttpOnly cookie with Secure and SameSite=Lax or Strict. Our guide to secure cookie flags explains each flag. Whatever you choose, reduce XSS risk with a solid Content Security Policy.
Should you use JWTs at all?
JWTs suit APIs, mobile apps, single sign-on and systems where several services need to verify identity without sharing a session database. For a traditional server-rendered website with one backend, an ordinary server-side session cookie is often simpler and easier to revoke. Don't adopt JWTs just because a tutorial did.
JWT security checklist
- Pin the accepted algorithm(s) in server config and reject
none. - Use asymmetric keys (RS256, ES256 or EdDSA) when more than one service verifies tokens.
- If you use HS256, use a long random secret stored outside your codebase.
- Validate
exp,nbf,issandaudon every request. - Keep access tokens short-lived and refresh tokens revocable.
- Keep personal and sensitive data out of the payload.
- Load verification keys only from trusted, fixed locations.
- Store browser tokens in
HttpOnlycookies where practical, and deploy a CSP. - Use a well-maintained JWT library and keep it updated.
- Serve everything over HTTPS, with strong security headers.
If you're building a SaaS product, our free tools for software and SaaS teams bring the token, encoding and header checks together.
FAQ
What is a JWT used for?
A JWT is mainly used for authentication and authorisation. After you log in, the server issues a signed token, and the client sends it with each request to prove who it is. JWTs are also used to pass identity between services in single sign-on, and as ID tokens in OpenID Connect.
Can anyone read a JWT?
Yes. The header and payload are only Base64url-encoded, so anyone with the token can decode them, for example with a JWT decoder. The signature only proves the contents haven't been changed. Never put passwords, secrets or sensitive personal data in a JWT unless it is encrypted using JWE.
What is the alg none vulnerability?
JWTs can declare alg: none, meaning no signature. Vulnerable libraries trusted that header, so attackers could edit the payload, set alg to none and remove the signature, and the token was accepted. Prevent it by configuring the exact algorithms your server accepts and rejecting unsigned tokens, as RFC 8725 recommends.
How long should a JWT last?
Access tokens should be short-lived, typically minutes rather than hours or days, because a stolen JWT usually stays valid until it expires. Pair them with a refresh token that can be rotated and revoked on the server. Always set exp, and remember it is measured in seconds since the Unix epoch.
Is it safe to store a JWT in localStorage?
It works, but any cross-site scripting flaw lets an attacker's script read and steal the token. An HttpOnly, Secure cookie with a suitable SameSite setting keeps the token away from JavaScript. That is generally safer for browser apps, provided you also handle CSRF. Either way, a strong Content Security Policy helps.
What's the difference between JWT, JWS and JWE?
JWT is the overall token format and set of claims. JWS (JSON Web Signature) is the signed form, and it's what people almost always mean by "a JWT". JWE (JSON Web Encryption) is the encrypted form, where the payload can't be read without the key. Most login systems use JWS.
Related reading
- UUID v4 vs v7: Which Should You Use for Database IDs?: UUID v4 vs v7 explained: how RFC 9562 defines each, why time-ordered v7 IDs suit database indexes, the collision odds, and when v4 is still the better pick.
More guides for your industry
Free checklist (PDF)
Free software & saas website checklist
31 practical checks for software & saas: website speed and mobile, Google Business Profile and reviews, email security, booking and enquiry handling, and AI search visibility. Each one says why it matters, and many link to a free tool that checks it.
Written by the MakeItFlow team
We build the website audit these guides are based on. Every fact is checked against the primary sources listed above and this guide was last reviewed on 25 October 2026 . How we research and check
Check your own site in 30 seconds
Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.