Understand JWT security deeply — algorithm confusion attacks, weak secrets, token storage, refresh token rotation, and implementation best practices.

Abdur Razzak
Full-Stack Web Developer
A JSON Web Token (JWT) consists of three base64url-encoded parts: header (algorithm and token type), payload (claims — user ID, roles, expiration), and signature. The signature is generated by the server using a secret key and verifies the token has not been tampered with. JWTs are self-contained — the server does not need to query a database to validate them, making them efficient for stateless authentication.
The 'none' algorithm attack: early JWT libraries accepted tokens with alg: 'none' (no signature) — always explicitly specify the algorithm and reject 'none'. The algorithm confusion attack: if your server accepts both RS256 (asymmetric) and HS256 (symmetric) algorithms, an attacker can trick the server into verifying an RS256 token using the public key as the HS256 secret — always hardcode the expected algorithm.
Never store JWTs in localStorage — it is accessible to JavaScript and vulnerable to XSS attacks. Store access tokens in memory (JavaScript variable) for maximum security. Store refresh tokens in HTTP-only cookies (inaccessible to JavaScript) with Secure and SameSite=Strict flags. This combination protects against both XSS (cannot steal access token) and CSRF (SameSite prevents cross-origin cookie submission).
Use short-lived access tokens (15 minutes) to limit the damage if a token is stolen. Use long-lived refresh tokens (7-30 days) stored in HTTP-only cookies to issue new access tokens without requiring re-login. Implement refresh token rotation: each time a refresh token is used, invalidate it and issue a new one. If you detect a used refresh token (replay attack), invalidate the entire session.
JWTs are stateless by design — the server cannot revoke them before expiration. Workarounds: maintain a blocklist (Redis set of revoked token JTI claims — check on every request), use very short expiry times, or switch to opaque tokens (random strings stored in database) for sessions that need instant revocation. For most applications, short expiry + refresh token rotation is a good balance.
Use a cryptographically random secret of at least 256 bits (32 bytes) — never a human-readable password. Generate with: openssl rand -base64 32. Store secrets in environment variables, never in code. Rotate secrets periodically by supporting multiple valid secrets during a transition period, then removing the old one after all tokens signed with it have expired. Use asymmetric keys (RS256) when you need to verify tokens in multiple services.