JWT security best practices
JWTs are secure when used carefully and dangerous when libraries are misconfigured. These practices follow RFC 8725 (JWT Best Current Practices) and the attacks seen in real systems.
Verification
- Always verify before trusting. Decoding is not verification. (RFC 8725 §3.1)
- Pin algorithms. Allow only the algorithms you issue with, and bind each key to one algorithm.
- Reject
alg: noneunless unsecured JWTs are an explicit, isolated requirement. (§3.2) - Validate issuer and audience on every token. (§3.8, §3.9)
- Use explicit typing (
typ) when one issuer produces several kinds of JWT, and check it. (§3.11)
Keys and secrets
- HMAC secrets must be random and at least as long as the hash: 32 bytes for HS256. Human-chosen secrets fall to offline brute force — an attacker needs only one token. (§3.5)
- RSA keys ≥ 2048 bits; prefer ES256 or PS256 for new systems when your ecosystem supports them.
- Rotate keys with distinct kid values and overlap periods.
- Never trust keys from the token (
jwk,jku,x5u,x5c) without validating them against a trust anchor or allowlist. (§3.10)
Claims and lifetimes
- Keep access tokens short-lived (minutes) and use refresh tokens for longer sessions.
- Don't put secrets or sensitive personal data in the payload — it is readable by anyone holding the token.
- Use
jtiplus server-side tracking for one-time tokens (password reset, email verification). - Treat every header value (especially
kid) as untrusted input — never concatenate it into file paths or SQL.
Transport and storage
- Send tokens only over HTTPS, in the
Authorizationheader. Avoid query strings: URLs end up in logs, history and Referer headers. - In browsers, prefer HttpOnly, Secure, SameSite cookies or in-memory storage over localStorage, which any XSS can read.
- Don't log full tokens. Log the
jtior a hash instead.
Classic attacks
alg: none
The attacker removes the signature and sets alg to none. Defeated by algorithm pinning.
RS256 → HS256 key confusion
The attacker signs with HMAC using your RSA public key as the secret. Defeated by binding keys to algorithms.
header: {"alg":"HS256","typ":"JWT"} ← was RS256
secret: -----BEGIN PUBLIC KEY-----… ← your public key
result: accepted by libraries that let the token pick the algorithmkid injection
A kid like ../../dev/null or x' UNION SELECT… abuses key lookup code. The decoder flags suspicious kid values.
Weak HMAC secrets
Tools can test billions of candidate secrets per second against a captured HS256 token. Use a generated 256-bit secret.
Cross-service replay
A token for service A is sent to service B, which skips the audience check. Always validate aud.
Paste a token to see automated observations — alg none, embedded keys, suspicious kid, lifetimes, weak secrets and more.
Run the security checksRelated: when to encrypt tokens.