JWT security best practices — 10 things developers get wrong
JWTs solve real problems — horizontal scaling without sticky sessions, OAuth/OIDC interop, and signed assertions between services — but they are easy to misuse. Below are ten mistakes teams make repeatedly, with practical fixes. For structure of the token itself, see JWT Explained.
1. Treating the payload as confidential
The middle segment is Base64URL-encoded JSON. Anyone with the JWT can decode it. Never put passwords, API keys, card numbers, or detailed PII in claims unless you also encrypt (JWE) — and most stacks stick to signed JWT (JWS) only.
2. Skipping signature verification
Decoding is not verifying. Your API must validate the signature with the correct key (HMAC secret or asymmetric public key), check alg against an allowlist, and reject tokens signed with unexpected algorithms — algorithm confusion between HS256 and RS256 has caused real breaches.
3. Ignoring expiry and clock skew
Always enforce exp (and usually nbf). Allow a small clock skew window (for example ±60 seconds) between issuers and validators so legitimate tokens are not rejected on boundary seconds.
4. Not constraining audience and issuer
Validate aud against your API’s identifier and iss against your identity provider’s issuer URL. A token minted for Service A must not authorize requests to Service B.
5. Weak secrets for HS256
Symmetric signing puts the entire security on one shared string. Use long, random secrets (≥256 bits of entropy), rotate them with a plan, and never embed secrets in client-side code — browsers cannot hold signing secrets safely for HS256-issued tokens consumed by the same browser.
6. Storing access tokens in localStorage
Any XSS payload can read localStorage. Prefer httpOnly, Secure, SameSite cookies for refresh/session flows where your threat model includes XSS — paired with tight CSP and short-lived access tokens.
7. Long-lived access tokens without rotation
Long expiry reduces traffic but increases blast radius when a token leaks. Prefer short access tokens plus refresh token rotation (one-time use refresh tokens stored server-side or in HttpOnly cookies).
8. No revocation story
Stateless JWT means validators do not ask a database — until you need instant revoke after compromise. Plan for blocklists, session versions, or rotating signing keys (kid headers). Pure “JWT means no DB” breaks down the moment you must invalidate tokens early.
9. Logging tokens verbatim
Request logs, APM traces, and error reports often capture Authorization headers. Log that a bearer token was present — never the full string — and scrub proxies accordingly.
10. Using JWT for everything by default
Server-side sessions with opaque IDs and Redis often simplify revocation, device binding, and logout. JWT shines for federation and cross-service claims; it is not mandatory for every SPA. Pick the model from threat model and ops constraints, not hype.
Bottom line
Secure JWT usage is mostly boring hygiene: verify signatures, narrow claims, short lifetimes, thoughtful storage, and explicit revocation. Decode in the browser for debugging — authorize only after cryptographic verification on the server.
Try it yourself
Use the free browser-based JWT Debugger on DevBench — no signup, runs entirely in your browser.
Open JWT Debugger