What Is a JWT? Structure, Signatures, and Common Mistakes
A JSON Web Token is three Base64URL segments joined by dots. Learn what each part holds, how the signature works, and why the payload is readable by anyone.
A JSON Web Token (JWT, pronounced "jot") is a compact string that carries a set of claims from one party to another. You will most often see it as a bearer token in an Authorization header, issued after login and presented on every subsequent request.
A JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNzU3NDYyNDAwfQ.k4Fq1Xz0T1rE6QmXbQzqz4vJq1w6iQWtWWy3Zp1nQ9c
Three segments, separated by two dots. Each segment is Base64URL-encoded. That single fact explains most of what people get wrong about JWTs, so it is worth understanding each part.
The three parts
1. Header
The first segment decodes to a small JSON object describing how the token was signed:
{ "alg": "HS256", "typ": "JWT" }
alg names the signing algorithm. HS256 is HMAC with SHA-256 (a shared secret). RS256 and ES256 use asymmetric keys, where the issuer signs with a private key and anyone can verify with the public key.
2. Payload
The second segment holds the claims — the actual data:
{ "sub": "1234567890", "name": "Jane Doe", "iat": 1757462400 }
A handful of claim names are standardized by RFC 7519:
| Claim | Meaning |
|---|---|
iss |
Issuer — who created the token |
sub |
Subject — who the token is about, usually a user ID |
aud |
Audience — which service the token is intended for |
exp |
Expiration time as a Unix timestamp |
nbf |
Not valid before this Unix timestamp |
iat |
Issued at |
jti |
Unique token ID, useful for revocation lists |
Everything else is up to the application. Roles, permissions, tenant IDs, and display names are all common.
3. Signature
The third segment is what makes the token trustworthy. For HS256, it is computed as:
HMAC-SHA256(
base64url(header) + "." + base64url(payload),
secret
)
A server that knows the secret can recompute this value and compare it to the signature in the token. If a single character of the header or payload was altered, the recomputed signature will not match and the token is rejected.
Base64URL is encoding, not encryption
This is the most important thing to internalize. The header and payload are not encrypted. Base64URL is a reversible transformation that exists only so the JSON can travel safely inside URLs and headers. Anyone who obtains the token can decode it and read every claim — no secret required.
Paste any JWT into the JWT Decoder and you will see the header and payload immediately. That is not a vulnerability in the tool; it is how the format works.
The practical rule: never put anything in a JWT payload that you would not show to the user. Passwords, API keys for other services, and internal notes do not belong there. If you genuinely need confidential claims, you want JWE (JSON Web Encryption), which is a separate specification.
Signed does not mean verified
A signature only proves something if someone checks it. A surprising number of bugs come from code that decodes the payload and trusts it without ever verifying the signature. Decoding is a Base64 operation; verifying requires the key.
Two related pitfalls:
alg: none. The specification allows an unsigned token with"alg": "none". Older libraries would accept such a token as valid. Any verification code should pin the expected algorithm rather than reading it from the token.- Algorithm confusion. If a server expects
RS256but a library lets the token chooseHS256, an attacker can sign a forged token using the server's public key as the HMAC secret. Again, the fix is to never let the token dictate the algorithm.
Expiry and revocation
Because a JWT is self-contained, the server does not need to look anything up to trust it. That is the appeal — and the drawback. Once issued, a token is valid until exp, and there is no built-in way to cancel it early.
Common mitigations:
- Keep access tokens short-lived (minutes, not days) and pair them with a refresh token that is stored server-side and can be revoked.
- Include a
jtiand keep a denylist of revoked IDs for the remaining lifetime of the token. - Rotate the signing secret if you suspect it leaked; every existing token becomes invalid at once.
Where to store it in a browser
If you use JWTs for a browser session, storing them in localStorage makes them readable by any script on the page, including a compromised dependency. An HttpOnly, Secure, SameSite cookie is not readable by JavaScript at all, which closes off that class of attack. The trade-off is that cookies are sent automatically, so you need CSRF protection.
Summary
- A JWT is
header.payload.signature, each part Base64URL-encoded. - The payload is readable by anyone with the token. Do not store secrets in it.
- The signature makes tampering detectable, but only if the receiver actually verifies it with a pinned algorithm.
- Tokens cannot be revoked on their own; keep them short-lived and plan for revocation separately.
To see all of this concretely, decode a token with the JWT Decoder, then recompute its HS256 signature yourself with the HMAC Generator using the same secret.