JSON Web Tokens are everywhere in modern authentication — and widely misunderstood. The biggest myth is that a JWT is encrypted and therefore safe to put secrets in. It isn't. This guide breaks down exactly what a JWT contains, what every claim means, how signing works, and where the real security boundary lies.
A JWT (JSON Web Token, RFC 7519) is a compact, URL-safe way to represent claims — statements about a subject — that are transferred between two parties. In practice you'll meet them most often as access tokens: after you log in, a server hands your client a JWT, and the client sends it back on every request to prove who it is.
"Compact" and "URL-safe" matter: a JWT is short enough to fit in an HTTP Authorization header and contains only characters that are safe in URLs and cookies.
JWTs are popular because they're stateless. A traditional session stores data on the server and gives the client an opaque id; the server must look that id up on every request. A JWT instead carries the claims inside the token, signed so the server can trust them without a database lookup.
A JWT is three Base64url-encoded segments joined by dots:
header.payload.signature
For example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWRhIn0.4pcPyMD0...
Each segment decodes to something human-readable. (Note: this is Base64url, a URL-safe variant — +// become -/_ and padding = is dropped.)
The header is a small JSON object describing how the token is signed:
{ "alg": "HS256", "typ": "JWT" }
alg — the signing algorithm (e.g. HS256, RS256).typ — the token type, usually JWT.kid — an optional key id that tells the verifier which key to use.The payload carries the claims. Several are registered as standard:
| Claim | Name | Meaning |
|---|---|---|
iss |
Issuer | Who created and signed the token |
sub |
Subject | Who the token is about (often a user id) |
aud |
Audience | Who the token is intended for |
exp |
Expiration | Unix time after which the token is invalid |
iat |
Issued At | Unix time the token was created |
nbf |
Not Before | Unix time before which the token is invalid |
jti |
JWT ID | A unique id, useful for revocation lists |
You can also add your own custom (private) claims — roles, tenant id, plan — but keep the token small, because it's sent on every request.
The signature is computed over the encoded header and payload using either a shared secret (HMAC, e.g. HS256) or a private key (RSA/ECDSA, e.g. RS256/ES256). It lets a recipient verify two things: that the token hasn't been tampered with, and that it was issued by someone holding the key.
| Family | Examples | Key model |
|---|---|---|
| HMAC | HS256, HS384, HS512 |
One shared secret signs and verifies |
| RSA | RS256, RS384, RS512 |
Private key signs, public key verifies |
| ECDSA | ES256, ES384 |
Like RSA but smaller keys/signatures |
Use HMAC when the same trusted service both issues and verifies tokens. Use RSA/ECDSA when many parties need to verify tokens but only one should be able to issue them (e.g. an identity provider publishing a public key).
This is the part that trips people up:
exp/nbf/aud/iss. Only this step proves the token is authentic and current.A JWT is signed, not encrypted. Decode it freely for debugging, but never store passwords, API keys, or other secrets in the payload — anyone with the token can read them. If you genuinely need a confidential payload, use JWE (JSON Web Encryption).
alg: none. A token claiming no signature must be rejected. Historically, libraries that honoured alg: none allowed trivial forgery.alg header downgrade an RS256 verifier into treating the public key as an HMAC secret.exp. Always reject expired tokens. Prefer short-lived access tokens plus a refresh token.jti denylist for emergencies.| JWT (stateless) | Session (stateful) | |
|---|---|---|
| Where state lives | In the token | On the server |
| Revocation | Hard (until exp) |
Easy (delete the session) |
| Scaling | No shared session store needed | Needs shared/sticky store |
| Size on the wire | Larger | Small opaque id |
Neither is universally "better" — JWTs shine for distributed services and APIs; server sessions shine when instant revocation matters.
The JWT Decoder splits a token into its header and payload and shows the exp/iat times in a readable form — without verifying the signature, and without sending the token anywhere. It runs entirely in your browser, so the token never leaves your device.
Is a JWT encrypted? No. It's signed and Base64url-encoded. Anyone can read the payload; the signature only guarantees integrity.
Can I trust a JWT I just decoded? Not on its own. Decoding proves nothing — you must verify the signature and check exp/aud/iss with the issuer's key.
Where should I store a JWT in a browser? Prefer an HttpOnly, Secure cookie to reduce XSS exposure; avoid localStorage for sensitive tokens.
What's the difference between a JWT and an opaque token? A JWT is self-describing and verifiable without a lookup; an opaque token is a random id that only means something to the server that issued it.
Is my token uploaded when I decode it here? No. The JWT Decoder runs entirely in your browser.