JWT Authentication
A JSON Web Token is a signed, base64-encoded structure carrying claims — user ID, expiry, roles — that a server can verify without consulting anything. The signature proves the token was issued by someone holding the key and has not been altered since.
That statelessness is the appeal. Traditional sessions require a lookup on every request; a JWT is verified with a signature check, so any server instance can authenticate a request with no shared session store. For HTTP’s stateless model and horizontally scaled deployments this fits naturally.
The cost, stated plainly
A JWT cannot be revoked. A valid signature and an unexpired exp claim mean the token is valid, and the server has nothing to check against. Logging out, changing a password, deleting an account, or revoking an admin role does not invalidate tokens already issued. They remain valid until they expire.
Every mitigation reintroduces state:
- Short-lived access tokens with refresh tokens. Access tokens expire in minutes; refresh tokens are long-lived, stored server-side, and revocable. This is the standard answer, and it means running a session store after all — for the refresh tokens.
- A revocation blocklist, checked on every request. This is a session lookup with extra steps.
The honest summary: JWTs are excellent where revocation genuinely does not matter, and where it does — which is most user-facing applications — you end up with a hybrid whose complexity exceeds plain sessions. Choosing JWTs for a single-server application with a database already in the request path is usually adopting the costs without the benefit.
Storage in the browser
localStorage is readable by any JavaScript on the page, so a single XSS vulnerability exfiltrates the token. An httpOnly cookie is not readable by JavaScript but is sent automatically, which reintroduces CSRF and needs SameSite handling.
Neither is clean. The cookie approach is generally better because XSS is more common than CSRF and SameSite=Strict is a solid mitigation, but it is a choice between exposures rather than a solution.
Two implementation points
Never accept the alg header from the token. Historic vulnerabilities allowed alg: none or algorithm confusion between HMAC and RSA. Configure the expected algorithm server-side and reject anything else.
JWTs are signed, not encrypted. The payload is base64, readable by anyone holding the token. Nothing confidential goes in it.
For websockets the handshake cannot carry an Authorization header, which is its own unresolved problem — see Study Duel.