Blog Details

  • Home
  • Blog
  • Breaking and Fixing JWTs: Real-World Vulnerabilities and Fixes
Breaking and Fixing JWTs: Real-World Vulnerabilities and Fixes

Breaking and Fixing JWTs: Real-World Vulnerabilities and Fixes

JSON Web Tokens are convenient but easy to misuse. The usual root causes are: incorrect signature verification, trusting tokens without validating claims, weak secrets, poor token storage, missing revocation, and bad key management. Fixes are straightforward: use well-maintained libraries, always verify signatures and claims, prefer strong asymmetric keys with JWKs and key rotation, use httpOnly secure cookies or secure storage, implement refresh token rotation and revocation, and log suspicious behavior.

A practical explanation of the problem

JWTs are compact tokens that carry a header, claims, and a signature. Problems occur when developers treat them as opaque session IDs or forget that the signature and claims must be validated on every request. In production that leads to anything from trivial bypasses to full account takeover. Most “real-world” JWT compromises come from implementation mistakes — not the JWT spec itself.

Common real-world JWT mistakes and why they’re bad

1. Accepting alg: "none" or trusting the token alg header

Some libraries historically accepted tokens where the header said alg: "none", meaning “no signature”. If the server does not explicitly reject unsigned tokens, an attacker can present unsigned tokens that the server treats as valid.

Why fix: Always require a signature and enforce allowed algorithms server-side.

2. Algorithm confusion and key type mix-ups

Using HMAC (symmetric) secrets with code that expects RSA/EC (asymmetric) or vice versa can open up logic errors where an attacker can craft tokens that verify with a public key as an HMAC secret.

Why fix: Enforce expected algorithm family (HS256 vs RS256) and validate key types. Never accept an algorithm from an untrusted source.

3. Weak secrets and predictable keys

Short or guessable HMAC secrets can be brute forced offline.

Why fix: Use long, randomly generated secrets for HMAC or, better, use asymmetric keys (RSA/ECDSA) and protect private keys.

4. Missing or lax claim validation

Not validating iss (issuer), aud (audience), exp (expiry), nbf (not before), iat (issued at) allows replay and misuse across services.

Why fix: Always validate all relevant claims strictly and use short access token lifetimes.

5. Storing tokens insecurely

Storing JWTs in localStorage or exposing them in URLs invites theft via XSS, referrers, or logs.

Why fix: Use httpOnly secure cookies with SameSite where appropriate, avoid tokens in query strings, and defend against XSS.

6. No revocation or refresh token rotation

Treating JWTs as permanently valid until expiry means compromised tokens cannot be revoked. Long-lived tokens increase risk.

Why fix: Use short-lived access tokens + rotating refresh tokens with server-side revocation or token introspection.

7. Poor key management and no rotation

Static keys that never rotate increase blast radius when leaked.

Why fix: Use a JWK endpoint, rotate keys, and plan key rollovers and key IDs (kid) handling.

Defensive code and configuration patterns

Below are secure patterns to adopt. These are defensive examples to implement on servers, not attack commands.

Reject alg: none and enforce allowed algorithms

Node.js example using jsonwebtoken (verify with explicit options):

const jwt = require('jsonwebtoken');

const publicKey = fs.readFileSync('/etc/keys/jwt_pub.pem');

function verifyToken(token) {

  // Only accept RS256 signed tokens, don't trust the token 'alg' header blindly

  const opts = { algorithms: ['RS256'], issuer: 'https://auth.example.com', audience: 'api://default' };

  return jwt.verify(token, publicKey, opts); // throws on invalid

}

 

Python PyJWT example:

import jwt

public_key = open('/etc/keys/jwt_pub.pem').read()

payload = jwt.decode(token, public_key, algorithms=['RS256'], issuer='https://auth.example.com', audience='api://default')

Validate critical claims every time

  1. exp — expiry. Reject expired tokens.
  2. nbf and iat — reject tokens used before issued.
  3. iss and aud — ensure token belongs to your issuer and audience.
  4. sub — check if relevant subject matches a resource owner.

Prefer asymmetric keys with JWKs for distributed systems

Host a JWKs endpoint that publishes public keys. Use kid in header to lookup the right key and rotate keys without downtime.

Flow:

  1. Auth server signs with private key and exposes public keys at /.well-known/jwks.json.
  2. Resource servers fetch and cache the JWKs, verify token signature based on kid.

Secure cookie storage for browser-based apps

For SPAs consider:

  1. Access token: short lived (e.g., 5–15 minutes).
  2. Refresh token: store in httpOnly, Secure, SameSite=strict cookie, with refresh rotation.
  3. All API requests send cookie automatically to your domain; protect against CSRF by using same-site cookies or anti-CSRF tokens.

Rotate and revoke refresh tokens

When a refresh token is used, issue a new one and mark the old one invalid. Keep minimal server-side state: a revocation list or token identifier in DB for refresh tokens.

Token introspection for opaque tokens

If you prefer statelessness but need revocation, use short-lived JWTs for access and opaque tokens for refresh or use a token introspection endpoint where resource servers can validate active sessions.

Example: secure refresh token rotation (conceptual)

  1. Client sends refresh token.
  2. Server verifies stored token ID and last use, issues a new access token and a new refresh token, invalidates the old refresh token.
  3. If an old refresh token is reused, it indicates theft. Revoke all sessions for that user.

This pattern mitigates replay of stolen refresh tokens.

Logging, monitoring, and alerts

  1. Log token verification failures with reason codes (signature, exp, aud, iss) — but avoid logging token contents.
  2. Alert on unusual patterns: many signature failures from a single IP, many tokens with same kid failing, sudden spike in expired token acceptance attempts.
  3. Monitor JWK fetch failures — if resource servers cannot fetch new keys after rotation, fail closed rather than accepting unknown tokens.

Testing and CI checks

  1. Add static checks to ensure no tokens or secrets are committed to repos.
  2. Include automated tests that verify libraries reject tokens with alg: none, missing claims, wrong aud/iss.
  3. Run dependency checks for JWT libs — keep them updated.

Realistic scenarios and lessons (brief)

Scenario A: A public key accidentally published as secret
A dev mistakenly stored the private RSA key in a public repo. Key rotated, but the team had no automated rotation process. Lesson: automation + short-lived keys reduce damage.

Scenario B: Access token leaked to third-party script via localStorage
A marketing widget loaded on pages had an XSS bug. Tokens in localStorage were stolen and used to call APIs. Lesson: do not store tokens in localStorage; prefer httpOnly cookies and harden against XSS.

Scenario C: Incomplete claim validation lets tokens issued for one service be accepted by another
Two microservices accepted tokens with different audiences. One accepted tokens for aud: service-a while the other accepted any aud. Attackers used a token from service A to call service B. Lesson: validate aud and iss per-resource.

Compact remediation checklist (copy into PR templates or runbooks)

Use well-maintained JWT libraries for your language.

Enforce signature verification and allowed algorithms server-side.

Prefer RS256/ES256 with protected private keys and JWKs published for verification.

Validate iss, aud, exp, nbf, iat, sub where applicable.

Keep access tokens short lived; implement rotating refresh tokens with server-side revocation.

Never store access tokens in localStorage; prefer httpOnly Secure SameSite cookies for browsers.

Do not pass tokens in query parameters.

Implement key rotation and monitoring for JWKs.

Log verification errors and set alerts for anomalous patterns.

Add CI tests that assert library behavior against known malformed tokens.

Further reading (standards and references)

  1. RFC 7519 JSON Web Token standard.
  2. RFC 7515 JSON Web Signature.
  3. OAuth 2.0 and OpenID Connect best practices (auth servers and token lifecycles).
  4. OWASP cheat sheets: JWT and session management.

 

© 2016 - 2025 Red Secure Tech Ltd. Registered in England and Wales under Company Number: 15581067