Glossary
What is a JWT (JSON Web Token)? How the signed pass works and how to use it safely
A JWT (JSON Web Token) is a tamper-proof 'pass' a server signs. How header/payload/signature work, and the dangers: skipping signature verification, allowing alg:none, and putting secrets in the payload.
"The signed pass a server hands you after login" — that's a JWT. Here's how it works and the key points for using it safely (no attack steps).
How it works (three parts)
A JWT is header.payload.signature joined with .. The header and payload are just base64url-encoded (not encrypted); only the signature is made with a secret (or shared) key.
1. header
meta like the signing algorithm. anyone can read it
2. payload
claims like user id and expiry. anyone can read = no secrets
3. signature
made with a key. guarantees no tampering
The key point is that the contents are "just readable." So the payload is not a place for passwords or personal data. A JWT's value is that by verifying the signature, the server can confirm "I issued this token and it hasn't been altered."
Defenses for using it safely
Always verify the signature, and pin the alg (most important)
Verify the signature server-side every time. Pin the accepted signature algorithm (alg) to the value you expect, and reject alg:none (no signature). Don't trust the alg written in the token's header blindly. The JWT best-practices RFC (RFC 8725) states that libraries MUST enable the caller to specify a supported set of algorithms and MUST NOT use any other algorithms, and that all cryptographic operations in the JWT must be validated, with the entire JWT rejected if any of them fail.
Close the swap, not just alg:none
Abusing alg is not only about removing the signature. RFC 8725 describes how changing RS256 to HS256 makes some libraries try to validate the signature using HMAC-SHA256 with the RSA public key as the shared secret (algorithm confusion). The public key is, by definition, public — so if that works, an attacker can mint a valid signature themselves. The defense is the same single point: do not trust the header's alg; pin the algorithm the server expects. Decide the algorithm first and then verify, rather than picking a verification routine from the key material you were handed.
Validate who it is for, and until when
A valid signature only tells you the token is genuine. Whether it is for you and still current is a separate check. RFC 7519 states that for aud, "if the principal processing the claim does not identify itself with a value in the 'aud' claim when this claim is present, then the JWT MUST be rejected", and that exp "identifies the expiration time on or after which the JWT MUST NOT be accepted for processing" (nbf works the same way at the other end). RFC 8725 adds that the application must validate that the keys used belong to the issuer. A token legitimately issued for a different service getting accepted by yours is what skipping these looks like.
Put no secrets in the payload
Assume anyone can decode the contents. Keep secrets — passwords, personal data, API keys — out of it. Include only claims that aren't catastrophic if seen, like identifiers and permissions.
Keep expiry short, and have a way to revoke
Make access tokens short-lived. An over-long expiry stretches the damage window if one is stolen. If you need revocation, pair a short-lived access token with a server-managed refresh/session.
Use a strong key and store it where it can't be stolen
Make the signing key strong, don't reuse it, and store it somewhere safe server-side. Protect the token itself too — keep it from being stolen via XSS, carry it in a cookie with safe attributes (→ beware theft via XSS).
This site's view: a JWT is not an all-purpose session
JWTs are often used to "hold login state," but they have a weakness: revocation is hard. If the signature is valid and unexpired, the server basically trusts it, so making a logout or invalidation take effect immediately is awkward. Our stance is: don't make a JWT a long-lived all-purpose session. Combine a short-lived access token with a server-side, revocable refresh/session, and you get both the JWT's upside (cheap verification) and easy revocation. Pick the right use for it.
Blind spot: "it decoded" is not "it's valid"
Because the contents are readable, anyone can paste a JWT into a JWT decoder and inspect the header and payload. But decoding (reading the contents) and verifying (confirming authenticity) are entirely different. Being able to decode guarantees nothing about validity. What decides whether a token is genuine is always the server-side signature verification. Reading contents for debugging is handy — just don't mistake "I could read it" for "it's valid."
Sources (primary)
- IETF RFC 8725, "JSON Web Token Best Current Practices" — datatracker.ietf.org (pinning algorithms, validating every cryptographic operation, algorithm confusion, keys belonging to the issuer)
- IETF RFC 7519, "JSON Web Token (JWT)" — datatracker.ietf.org (the normative definitions of
exp,nbfandaud)
Read next
- Tool: JWT decoder / inspector (check contents, spot alg:none or expiry)
- Design: password reset design flaws (the pitfalls on the token-issuing side)
- Glossary: what XSS is (a classic token-theft route) / what CSRF is
- Learn: choosing MFA the right way
FAQ
QIs the content of a JWT encrypted?
No. A standard JWT's header and payload are 'encoded' with base64url, not encrypted, so anyone holding the token can read the contents. So never put secrets like passwords or personal data in the payload. What a JWT protects is not 'confidentiality of contents' but 'that it hasn't been tampered with' (integrity, via the signature).
QWhat's the most dangerous JWT setting?
Not verifying the signature, or accepting 'alg:none' (no signature). Allowing that lets an attacker pass a forged token with freely rewritten contents. The defense is to always verify the signature server-side and pin the accepted signature algorithm (alg) to the value you expect.
QAre decoding and verifying the same thing?
No. Decoding just reads the contents and anyone can do it (you can check with a JWT decoder). Verifying is the server confirming, with a secret/public key, that the signature is valid, the token is unexpired, and the issuer/audience are correct. 'It decoded' does not mean 'it's a valid token' — server-side verification decides authenticity.