The Problem JWTs Solve
HTTP is stateless. When a client makes a request to a server there is no built-in way for the server to know which user is on the other end. The classic solution is a session cookie: the server stores a session id and the client's data behind it, then the cookie is attached to every subsequent request. That works but requires the server to keep a session store and look up the data on every request.
JWTs invert the model. Instead of the server holding the state and the client holding a reference, the client holds the state in a signed, tamper-proof string. The server does not look anything up; it just verifies the signature and trusts the claims inside. This is why JWTs are called stateless tokens and why they became the default for modern API authentication.
Anatomy
A JWT is three Base64URL-encoded strings separated by dots: header.payload.signature. The header and payload are JSON. The signature is binary.
A typical header:
{
"alg": "HS256",
"typ": "JWT"
}
A typical payload:
{
"sub": "user_12345",
"name": "Jane Doe",
"role": "admin",
"iat": 1730000000,
"exp": 1730003600
}
And a signature which is just bytes rendered as Base64URL text.
The Standard Claims
RFC 7519 defines seven reserved claim names. Any application can add custom claims, but these seven mean the same thing everywhere:
- iss issuer, the party that minted the token
- sub subject, usually the user id
- aud audience, who the token is for
- exp expiration time (Unix seconds)
- nbf not-before time
- iat issued-at time
- jti unique token id, used for revocation lists
Signing Algorithms
The alg field in the header names the signing algorithm. The common ones:
- HS256, HS384, HS512 symmetric HMAC with SHA-2. Fast, requires both sides to share a secret.
- RS256, RS384, RS512 RSA signatures. Asymmetric: issuer holds the private key, verifiers only need the public key.
- ES256, ES384, ES512 ECDSA signatures. Asymmetric and much shorter than RSA for the same security.
- EdDSA modern Edwards curve signatures. The current best choice for new systems.
The alg:none Pitfall
The JWT spec allows an alg value of none which means the token is not signed. The feature exists for internal pipelines where signing happens elsewhere, but it has been the source of some of the most embarrassing JWT CVEs in history. A naive verifier reads the alg header, sees none and skips signature verification entirely. An attacker who crafts a token with alg: none can make the server believe anything in the payload.
none. Reject anything your library does not recognise.
Key Confusion
The second famous JWT vulnerability is key confusion. A server that uses RS256 holds a public key for verification. If the server library is configured to auto-detect the algorithm from the header, an attacker can switch the alg to HS256 and sign the token using the public key as the shared secret. Since the server treats the public key as the HMAC secret, verification succeeds.
The fix is the same as alg:none: pin the algorithm on the server side. Never trust the header to tell you which algorithm to use.
Expiry, Refresh and Revocation
The strongest objection to JWTs is that they cannot be revoked before they expire. If a user's device is stolen with a two-week access token in it, the server has no way to stop the token from being used. The common patterns to work around this:
- Short-lived access tokens: 15 minutes is typical. Combined with a refresh token held in HttpOnly cookies.
- Deny list: store the
jtiof any revoked token in a fast key-value store. Check on each request. This re-introduces statefulness but at a tiny fraction of the cost of full session lookups. - Rotate signing keys: invalidate all tokens by rotating the key.
When Not to Use a JWT
- Browser session for a regular web app: use a signed session cookie. Simpler, revocable, no JWT library needed.
- Storing secret information: JWT is signed, not encrypted. Anyone with the token can read the payload.
- Long-lived tokens for sensitive actions: short expiry plus refresh is almost always a better design.
Decode One Yourself
Paste any JWT into the Toolfrnd JWT Decoder to see the header, payload and signature laid out with claim summaries and an expiration status badge. The decoder does not verify signatures, only reads structure. If you also need to inspect the exp timestamp in your local time, pair it with the Timestamp Converter.
Takeaway
JWTs are a great fit for stateless API auth and single-sign-on. They stop being a good fit the moment you need instant revocation or secret payloads. Pin your algorithms, keep tokens short-lived, and never use alg: none in production.