JWT Security Best Practices

Common vulnerabilities, attack vectors, and how to defend against them

1. Security Overview

JWTs are a secure mechanism for transmitting claims — when implemented correctly. The specification itself is well-designed, but the flexibility it offers means developers can easily make mistakes that lead to serious vulnerabilities. Most JWT security issues come from implementation errors, not flaws in the standard.

The fundamental security properties of a JWT are:

  • Integrity — The signature proves the token hasn't been modified
  • Authentication — The signature proves the token was issued by the expected party
  • No confidentiality — The payload is encoded, not encrypted; anyone can read it

Critical: A JWT is only as secure as its signature verification. If an attacker can bypass signature checks, they can forge any token with any claims they want.

2. Algorithm Confusion Attacks

Algorithm confusion (also called "key confusion" or "algorithm substitution") is one of the most dangerous JWT vulnerabilities. It occurs when a server uses the algorithm specified in the token's header to decide how to verify the signature, rather than using a fixed algorithm.

How the Attack Works

Consider a server that normally verifies tokens with RS256 (asymmetric). The public key is well-known. An attacker can:

  1. Take the server's public key (which is available to anyone)
  2. Create a new token with the header {"alg": "HS256"}
  3. Sign the token using HMAC with the public key as the HMAC secret
  4. The server reads the header, sees "HS256", and uses the public key as the HMAC secret to verify
  5. Verification succeeds — the attacker has forged a valid token

Defense

  • Never trust the alg header — Configure your verification library with the expected algorithm explicitly
  • Use an allowlist — Only permit the specific algorithms your application uses
  • Separate keys by algorithm — Use different keys for HMAC and RSA/ECDSA so the attack is impossible even if the algorithm is swapped

3. The "none" Algorithm Vulnerability

The JWT specification defines an "unsecured JWT" with "alg": "none" — a token with no signature at all. This exists for cases where the token's integrity is guaranteed by other means (such as a TLS channel), but many JWT libraries historically accepted none by default.

// Malicious token with no signature
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.
eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJzdXBlcnVzZXIifQ.
// (empty signature)

Defense

  • Reject "none" algorithm — Explicitly disallow it in your verification configuration
  • Always require a signature — Never accept tokens without a valid signature in production
  • Keep libraries updated — Modern JWT libraries reject none by default, but older versions may not

4. Weak HMAC Secrets

When using HMAC algorithms (HS256, HS384, HS512), the security of the entire system depends on the secret key. If the secret is weak, an attacker can brute-force it offline using the token itself.

The Brute-Force Attack

Since JWTs are self-contained, an attacker who obtains a single valid token can attempt to guess the HMAC secret offline. They compute HMAC(header.payload, guess) for each candidate secret and compare the result to the token's signature. Tools like hashcat andjwt-cracker can test millions of candidates per second.

Defense

  • Use cryptographically random secrets — Generate secrets with a CSPRNG, not a password
  • Minimum key lengths:
    • HS256: 32 bytes (256 bits)
    • HS384: 48 bytes (384 bits)
    • HS512: 64 bytes (512 bits)
  • Consider asymmetric algorithms — RS256 or ES256 eliminate the shared-secret problem entirely
  • Rotate secrets periodically — Use key rotation with kid (Key ID) to support graceful transitions

5. Token Leakage

A stolen JWT gives the attacker the same access as the legitimate user until the token expires. Unlike session IDs, there's no server-side session to invalidate. Common leakage vectors include:

  • URL parameters — Tokens in query strings are logged by proxies, browsers, and web servers. Always send tokens in HTTP headers or request bodies, never in URLs.
  • Browser storage (localStorage) — Accessible to any JavaScript on the page, making it vulnerable to XSS attacks. If an attacker injects a script, they can exfiltrate the token.
  • Server logs — Authorization headers may be logged by reverse proxies, API gateways, or application logging. Ensure tokens are excluded from logs.
  • Referrer headers — If a page includes the token in its URL, the full URL may leak to external sites via the Referer header.
  • Browser extensions — Malicious or compromised extensions can read tokens from storage or intercept HTTP requests.

6. Token Lifetime & Revocation

One of JWT's biggest trade-offs is that tokens are valid until they expire. Unlike server-side sessions, you can't simply delete a JWT to log someone out. This makes token lifetime critically important.

Recommended Lifetimes

Token TypeLifetimeRationale
Access token5–60 minutesShort window limits damage from theft
Refresh token7–30 daysLong-lived but stored securely and revocable
ID token5–15 minutesUsed once to establish identity, then discarded

Revocation Strategies

  • Token blacklist — Store revoked jti values in a fast store (Redis) and check on every request. Trades some of JWT's stateless benefit for revocation capability.
  • Short-lived tokens + refresh rotation — Use very short access tokens (5 min) with refresh tokens that are rotated on each use. Revoke the refresh token to block access.
  • Token versioning — Store a version counter per user. Include the version in the token. Increment the counter to invalidate all existing tokens for that user.

7. Claim Injection & Manipulation

If an application allows users to influence claim values without proper validation, attackers may escalate their privileges. Common scenarios:

  • Role escalation — An API that reads the role claim and the registration endpoint allows users to specify their role. Always set sensitive claims server-side.
  • Tenant hopping — In multi-tenant systems, the tenant_id claim must be set by the auth server, never accepted from user input.
  • Audience bypass — If aud is not validated, a token meant for Service A can be replayed against Service B.

Rule of thumb: The auth server is the only entity that should set claim values. Resource servers should only read and validate claims, never trust external input to populate them.

8. Secure Token Storage

Where you store JWTs in the browser has significant security implications:

localStorage

  • Persists across tabs and page reloads
  • Accessible to any JavaScript on the page
  • Vulnerable to XSS — a single script injection can steal the token

httpOnly Cookie

  • Not accessible from JavaScript (immune to XSS)
  • Sent automatically with every request
  • Requires CSRF protection (SameSite attribute or CSRF tokens)

Recommendation: Use httpOnly, Secure, SameSite=Strict cookies for token storage. This protects against XSS (the most common web vulnerability) while theSameSite attribute mitigates CSRF.

9. Production Security Checklist

Use this checklist when deploying JWT-based authentication to production:

  • Algorithm is explicitly configured server-side (never read from the token header)
  • The "none" algorithm is rejected
  • HMAC secrets are at least 256 bits, generated with a CSPRNG
  • RSA keys are at least 2048 bits
  • Token expiration (exp) is set and validated on every request
  • Issuer (iss) and audience (aud) are validated
  • Tokens are transmitted over HTTPS only
  • Tokens are not included in URLs or query parameters
  • Tokens are stored in httpOnly cookies (not localStorage)
  • Sensitive claims (roles, permissions) are set server-side only
  • No sensitive data (passwords, PII) is stored in the payload
  • Token revocation strategy is in place (blacklist, short lifetimes, or version counters)
  • JWT library is up to date and from a trusted source
  • Key rotation mechanism is in place using kid headers

Test Your JWT Implementation

Use our decoder to inspect your tokens and verify signatures before deploying to production.