Vulnerability library  /  JWT weaknesses

JSON Web Token (JWT) weaknesses

JWT weaknesses

JWT weaknesses occur when applications decode tokens without fully validating their signature, algorithm, issuer, audience, lifetime, and key source. Attackers may forge identities or privileges that the server treats as trusted.

Also known as
JWT validation flaws · Insecure JWT handling · Token signature bypass
Typical severity · Medium to Critical
Last updated
On this page: What it is
  1. 1 · What it is
  2. 2 · Why it matters
  3. 3 · Examples
  4. 4 · How to fix it
  5. 5 · How to test for it
  6. References
  7. Related

What it is#

A JWT is a compact set of claims that may be signed or encrypted. The common signed form has a header, payload, and signature. The header and payload are encoded, not secret; anyone holding the token can normally read them.

Security depends on verification policy. The application must choose which algorithms, issuers, audiences, keys, and token types it trusts. A library that merely decodes the payload has not authenticated it.

Frequent problems include accepting unsigned tokens, algorithm confusion, weak HMAC secrets, trusting token-supplied key URLs, unsafe kid handling, missing expiration checks, accepting an ID token as an API access token, and trusting role claims from the wrong issuer.

Why it matters#

  • Forge another user's identity or administrative role.
  • Reuse expired, revoked, or incorrectly scoped tokens.
  • Present a token issued for a different application or API.
  • Bypass tenant boundaries through trusted but unvalidated claims.
  • Expose personal or secret data placed in the readable payload.
  • Extend compromise when long-lived bearer tokens cannot be revoked.
Typical severity

Medium for limited lifetime or disclosure issues. High to critical when an attacker can forge signatures, select trusted keys, or convert an unrelated token into administrative API access. Actual severity depends on context: who is affected, what data or actions are reachable, and existing controls.

Examples#

✕ Vulnerablejavascript
// Vulnerable: decode() parses claims but does not verify authenticity.
import jwt from 'jsonwebtoken';

app.get('/api/profile', (req, res) => {
  const token = req.headers.authorization?.replace(/^Bearer\s+/i, '');
  const claims = jwt.decode(token);
  res.json(loadProfile(claims.sub));
});
✓ Fixedjavascript
// Fixed: pin the algorithm and validate issuer, audience, and lifetime.
import jwt from 'jsonwebtoken';

const verificationKey = process.env.ACCESS_TOKEN_PUBLIC_KEY;

app.get('/api/profile', (req, res) => {
  const token = req.headers.authorization?.replace(/^Bearer\s+/i, '');
  const claims = jwt.verify(token, verificationKey, {
    algorithms: ['RS256'],
    issuer: 'https://identity.example.com/',
    audience: 'api://customer-portal',
    clockTolerance: 5,
    maxAge: '15m',
  });

  res.json(loadProfile(claims.sub));
});

Production code must also handle missing tokens and verification errors without returning sensitive details.

How to fix it#

1. Use a maintained JOSE library and its verification API. Do not implement cryptography or token parsing yourself.

2. Pin allowed algorithms. Configure the expected asymmetric or symmetric algorithm server-side. Never select the algorithm solely from the untrusted header, and never accept none for authenticated tokens.

3. Validate the complete trust context. Require exact issuer and audience values, expiration, not-before time where used, token type, and any application-specific scope or tenant claims.

4. Control key selection. Fetch keys only from preconfigured trusted issuers over HTTPS. Do not follow arbitrary jku or x5u values. Treat kid as an identifier into an allowlisted key set, not a filename, query, or URL.

5. Separate token purposes. Access tokens, ID tokens, refresh tokens, email-verification tokens, and password-reset tokens are not interchangeable. Use distinct audiences, types, validation paths, and preferably keys.

6. Protect signing keys. Store private and symmetric keys in a managed key service or secret store, rotate them, retain old public keys only for the required validation window, and audit key use.

7. Keep tokens short lived. Use refresh-token rotation or server-side session state where revocation is required. Revoke sessions after credential changes and suspected compromise.

8. Keep secrets out of claims. Signed JWT payloads remain readable. Store only the minimum identifiers and authorization context required.

9. Authorize after verification. A valid token proves only what its trusted issuer asserted. The API must still enforce object, tenant, and function-level authorization.

How to test for it#

  • Obtain test tokens through the normal identity flow and document expected issuer, audience, algorithm, type, scope, and lifetime.
  • Modify the header, payload, and signature separately and confirm every altered token is rejected.
  • Verify rejection of unsigned tokens, unexpected algorithms, expired tokens, future not-before values, wrong issuer, wrong audience, and wrong token type.
  • Present an ID token where an access token is expected and a token for one test API to another. Both must fail.
  • Test unknown and malformed key identifiers and untrusted key URLs without contacting attacker infrastructure.
  • Rotate a test signing key and confirm the intended overlap and retirement behavior.
  • Verify logout, password reset, account disablement, and privilege changes have the documented effect on existing tokens.
  • Confirm logs never store complete bearer tokens and error responses do not reveal signature or key-selection internals.

References#

Permalinklibrary.infinity8labs.com/jwt-weaknesses/JSON
Found this in Breachvane? Your finding links here for background. Evidence, affected assets and retest status are in your Breachvane ↗ (opens in a new tab) workspace. Need help fixing it? Talk to us.