What a JWT is
A JSON Web Token is three Base64url-encoded segments separated by dots: header.payload.signature.
The header declares the token type and signing algorithm. The payload holds the claims — the actual data. The signature is computed over the first two segments using a secret or private key, and is what makes the token tamper-evident.
This decoder reads the header and payload only. It does not verify the signature, because that requires the key — which should never be pasted into a web page.
Encoded is not encrypted
This is the most important and most frequently misunderstood point about JWTs.
Base64url is an encoding, not encryption. Anyone holding the token can decode the payload and read every claim in it, with no key and no effort. The signature prevents modification; it does nothing to prevent reading.
The practical rule: never put anything confidential in a JWT payload. No passwords, no personal data beyond what is needed, no internal identifiers you would not publish. Assume the payload is public, because functionally it is.
Standard claims worth knowing
iss— issuer, who created the tokensub— subject, usually the user identifieraud— audience, the intended recipientexp— expiry, as a Unix timestamp in secondsnbf— not valid before this timeiat— issued atjti— a unique token identifier, useful for revocation lists
A frequent bug: exp is in seconds, while JavaScript's Date.now() returns milliseconds. Comparing them directly makes every token appear either permanently valid or long expired.
The alg:none vulnerability
A well-known class of JWT vulnerability comes from trusting the header's algorithm declaration.
The specification permits "alg": "none" for unsigned tokens. A naive verifier reads the algorithm from the header and, seeing none, skips verification entirely — allowing an attacker to forge any payload they like.
A related attack exploits algorithm confusion: a token signed with the symmetric HS256 algorithm, using the server's public RSA key as the HMAC secret. A verifier that reads the algorithm from the token rather than enforcing its own will accept it.
The defence in both cases is the same: the server must specify the expected algorithm rather than reading it from the token. Reputable libraries now require this explicitly.
The revocation problem
JWTs are stateless by design, which is their main advantage and their main limitation. Because the server does not store session state, it also cannot easily invalidate a token before it expires.
When a user logs out, changes their password, or has access revoked, any issued token remains valid until exp. Common mitigations:
- Short expiry — 5 to 15 minutes for access tokens, paired with a longer-lived refresh token that can be revoked centrally.
- A denylist of revoked
jtivalues — which reintroduces the server state JWTs were meant to avoid, though only for a small set. - A token version claim compared against a per-user counter, allowing bulk invalidation on password change.
If you need immediate revocation and are already running a database, traditional server-side sessions are often the simpler and better choice. JWTs earn their keep in distributed systems where checking a central session store on every request is the thing you are trying to avoid.
Frequently asked questions
Is a JWT encrypted?
No. The header and payload are Base64url encoded, which anyone can decode without a key. The signature prevents modification but not reading. Never put confidential data in a JWT payload.
Why does my token appear expired when it should not?
The exp claim is in seconds, while JavaScript's Date.now() returns milliseconds. Comparing them directly is a very common bug that makes tokens appear permanently valid or long expired.
What is the alg:none vulnerability?
A verifier that reads the signing algorithm from the token's own header can be tricked into skipping verification when the header declares none. The server must specify the expected algorithm rather than trusting the token.
How do I revoke a JWT before it expires?
You largely cannot, which is the trade-off for statelessness. Use short expiry with revocable refresh tokens, a denylist of token identifiers, or a per-user token version claim.