taptools/blog/JWT Security Mistakes
SecurityMay 15, 2026 · 9 min read

JWT Security Mistakes Developers Make

JWT is widely used but widely misimplemented. These are the most critical security mistakes developers make with JWT — and how to fix them.

Mistake 1 — Using the "none" algorithm

Some JWT libraries accept tokens with alg: "none" in the header. This means no signature — anyone can create a valid token by simply setting the algorithm to none and removing the signature.

Attack example:

// Attacker creates this header
{ "alg": "none", "typ": "JWT" }

// With this payload
{ "sub": "admin", "role": "admin" }

// No signature needed!
// Vulnerable servers accept this as valid

Fix:

// Always specify allowed algorithms
jwt.verify(token, secret, { 
  algorithms: ['HS256']  // never include 'none'
})

Mistake 2 — Storing sensitive data in JWT payload

The JWT payload is Base64 encoded — not encrypted. Anyone who intercepts your token can decode and read the payload without knowing your secret key.

Never store in JWT:

// ❌ NEVER put these in JWT payload
{
  "password": "user_password",
  "creditCard": "4111111111111111",
  "ssn": "123-45-6789",
  "secretApiKey": "sk_live_xxx"
}

Store only what's needed:

// ✅ Safe to store in JWT
{
  "sub":   "user_id_123",
  "role":  "admin",
  "email": "user@example.com",
  "iat":   1716000000,
  "exp":   1716003600
}

Mistake 3 — Weak secret keys

Short or predictable HS256 secrets can be brute forced. Attackers can use tools like hashcat to crack weak JWT secrets offline once they have a token.

// ❌ Weak secrets
"secret"
"password"
"jwt_secret"
"1234567890"

// ✅ Generate strong secrets
// Node.js
require('crypto').randomBytes(64).toString('hex')

// Result:
// a8f5f167f44f4964e6c998dee827110c
// d14a028c2a3a2bc9476102bb288234c4
// (64 bytes = 128 hex characters)

Mistake 4 — Not validating claims

Verifying the signature is not enough. You must also validate the claims — especially iss (issuer),aud (audience) and exp (expiry).

// Always validate these claims
jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer:     'https://auth.yourapp.com',
  audience:   'https://api.yourapp.com',
  // exp is checked automatically
})

Mistake 5 — Storing JWT in localStorage

localStorage is accessible to any JavaScript running on your page. If your site has an XSS vulnerability an attacker can steal all JWT tokens stored in localStorage.

StorageXSS RiskCSRF RiskRecommendation
localStorage❌ High✅ NoneAvoid for auth tokens
sessionStorage❌ High✅ NoneAvoid for auth tokens
httpOnly Cookie✅ Safe⚠️ Medium✅ Recommended
Memory (React state)✅ Safe✅ None✅ Best for SPAs

JWT Security checklist

Always specify allowed algorithms in jwt.verify()
Never use alg: "none"
Use a secret key of at least 256 bits (32 bytes)
Validate iss, aud and exp claims
Never store sensitive data in the payload
Use httpOnly cookies or memory storage — not localStorage
Set short expiry times and implement token refresh
Use RS256 for distributed systems and microservices
Rotate secrets regularly
Never log or expose JWT tokens

Inspect your JWT tokens safely

Our JWT Decoder runs entirely in your browser. Your tokens never leave your device.

Open JWT Decoder →