alltools.one
Securityβ€’
2025-06-12
β€’
8 min
β€’
alltools.one Team
JWTSecurityAuthenticationAPIWeb Security

JSON Web Token Security: Common Vulnerabilities and Fixes

JWTs are the de facto standard for API authentication, but their apparent simplicity hides real security pitfalls. Misconfigured JWTs have led to authentication bypasses, privilege escalation, and data breaches. This guide covers the most critical vulnerabilities and how to prevent them.

JWT Structure Recap

A JWT consists of three Base64URL-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
  • Header: Algorithm and token type
  • Payload: Claims (user data, expiration, etc.)
  • Signature: Cryptographic verification

Inspect JWT structure with our JWT Encoder/Decoder.

For a foundational understanding of JWT structure, see our JWT Tokens Explained guide.

Critical Vulnerabilities

1. Algorithm Confusion Attack

The most dangerous JWT vulnerability. If a server accepts the alg header from the token without validation, an attacker can:

Attack: Change the algorithm from RS256 (asymmetric) to HS256 (symmetric) and sign the forged token with the server's public key:

// Attacker's forged token
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// Signed with the server's PUBLIC key as the HMAC secret

If the server verifies HS256 tokens using the public key as the secret, the forged token passes verification.

Fix: Always specify the expected algorithm explicitly:

// WRONG - accepts whatever algorithm the token specifies
jwt.verify(token, key);

// CORRECT - enforce specific algorithm
jwt.verify(token, key, { algorithms: ['RS256'] });

2. None Algorithm Attack

Some libraries accept "alg": "none" β€” a token with no signature:

// Forged token with no signature
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: ""  // empty

Fix: Never allow none algorithm in production. Explicitly whitelist allowed algorithms.

3. Weak Signing Secrets

HMAC-based JWTs (HS256/HS384/HS512) are only as strong as the secret:

// TERRIBLE - can be brute-forced in seconds
secret = "password123"

// WEAK - dictionary attack vulnerable
secret = "my-jwt-secret"

// STRONG - 256+ bits of randomness
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"

Fix: Use at least 256 bits of cryptographic randomness for HMAC secrets. Better yet, use asymmetric keys (RS256, ES256) where the signing key never needs to be shared.

4. Missing Expiration

Tokens without expiration never expire β€” a stolen token grants permanent access:

// WRONG - no expiration
{ "sub": "user123", "role": "admin" }

// CORRECT - short expiration
{
  "sub": "user123",
  "role": "admin",
  "exp": 1705312800,
  "iat": 1705309200,
  "nbf": 1705309200
}

Best practice: Access tokens expire in 15-60 minutes. Use refresh tokens (stored securely) for long sessions.

5. Sensitive Data in Payload

JWT payloads are Base64URL-encoded, not encrypted. Anyone can decode them:

echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}

Never include: Passwords, API keys, credit card numbers, personal data (SSN, medical records), or any secret in JWT payloads.

6. Token Storage (XSS vs CSRF)

StorageXSS VulnerableCSRF Vulnerable
localStorageYesNo
Cookie (no flags)YesYes
HttpOnly CookieNoYes
HttpOnly + SameSite CookieNoNo

Recommended: Store JWTs in HttpOnly, Secure, SameSite=Strict cookies. This prevents JavaScript access (XSS defense) and cross-site requests (CSRF defense).

Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

Security Checklist

  1. Explicitly specify algorithms β€” never accept from token header
  2. Reject none algorithm β€” always require a signature
  3. Use strong secrets β€” 256+ bits for HMAC, 2048+ bits for RSA
  4. Set short expiration β€” 15-60 minutes for access tokens
  5. Validate all claims β€” exp, iss, aud, nbf
  6. Store in HttpOnly cookies β€” not localStorage
  7. Rotate secrets periodically β€” plan for key rotation
  8. Never store sensitive data in the payload
  9. Use asymmetric keys for distributed systems (RS256 or ES256)
  10. Implement token revocation β€” blacklist or short expiration + refresh tokens

Asymmetric vs Symmetric Signing

AspectHS256 (Symmetric)RS256 (Asymmetric)
KeyShared secretPrivate/public key pair
Who can signAnyone with the secretOnly the private key holder
Who can verifyAnyone with the secretAnyone with the public key
Key distributionSecret must be shared securelyOnly public key is shared
PerformanceFasterSlower
Best forSingle serviceMicroservices, distributed systems

Recommendation: Use ES256 (ECDSA) for new applications β€” it provides asymmetric security with performance close to HMAC.

Token Revocation Strategies

JWTs are stateless by design β€” there is no built-in way to revoke them. Strategies:

  1. Short expiration: If tokens expire in 15 minutes, a stolen token's window is limited
  2. Refresh token rotation: Issue a new refresh token with each use; if a refresh token is used twice, revoke all tokens
  3. Blacklist: Store revoked token IDs (jti claims) and check on each request
  4. Token versioning: Include a version number in claims; increment the user's version on logout

FAQ

Should I use JWT or session cookies for authentication?

Session cookies are simpler and more secure for traditional web applications β€” the server controls session lifecycle, and revocation is instant. JWTs are better for stateless APIs, microservices, and mobile apps where server-side session storage is impractical. If you choose JWTs, implement the security measures in this guide.

What is the ideal JWT expiration time?

For access tokens: 15-60 minutes. Shorter is more secure but requires more frequent refresh. For refresh tokens: 1-30 days, stored in HttpOnly cookies. For single-use tokens (email verification, password reset): 1-24 hours.

Related Resources

Published on 2025-06-12
JSON Web Token Security: Common Vulnerabilities and Fixes | alltools.one