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.

By the MakeItFlow team Updated 10 min read
Share LinkedIn X Facebook WhatsApp
What Is a JWT? JSON Web Tokens Explained, With the Security Mistakes 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:

  • exp is in the future (allow a small clock skew of a minute or two at most)
  • nbf, if present, is in the past
  • iss is the issuer you trust
  • aud matches your service, so a token issued for one app can't be replayed against another
  • the user or tenant in sub still 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

  1. Pin the accepted algorithm(s) in server config and reject none.
  2. Use asymmetric keys (RS256, ES256 or EdDSA) when more than one service verifies tokens.
  3. If you use HS256, use a long random secret stored outside your codebase.
  4. Validate exp, nbf, iss and aud on every request.
  5. Keep access tokens short-lived and refresh tokens revocable.
  6. Keep personal and sensitive data out of the payload.
  7. Load verification keys only from trusted, fixed locations.
  8. Store browser tokens in HttpOnly cookies where practical, and deploy a CSP.
  9. Use a well-maintained JWT library and keep it updated.
  10. 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.

#jwt #authentication #api security #web development

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.

We email you the PDF and nothing else unless you tick the box. See the privacy policy.

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

Share LinkedIn X Facebook WhatsApp

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.

Weekly newsletter

One practical website fix, every week

New guides on speed, SEO, email deliverability and security — straight to your inbox. No spam, unsubscribe in one click.

Keep reading