Learn

What are JWT security weaknesses?

JWT weaknesses come from weak signature checks, loose claim validation and poor key handling. Learn the impact and how to prevent and test for them.

Updated 2026-10-15

A JSON Web Token (JWT) is a signed token that tells a server who a user is and what they may do. JWT weaknesses are mistakes in how an app checks those tokens: accepting a token without verifying its signature, trusting the algorithm the token itself names, skipping expiry or audience checks, or using weak keys. CWE tracks the core issue as improper verification of a cryptographic signature (CWE-347), and the IETF publishes best practices in RFC 8725.

Updated October 2026

Why it matters

The token is the user's identity for every request. If the server accepts a token it should reject, a user can claim to be someone else, keep access after logout, or carry a token from one service into another. In a multi-tenant app, a loosely checked tenant or role claim can open other customers' data.

JWT bugs also spread. Tokens are often shared across many services, and each service may verify them a little differently. The weakest service sets the security level for all of them. A single internal API that decodes without verifying can undo careful work everywhere else. Treat token checks as shared infrastructure, owned and reviewed by one team.

A simple illustration

Acme Corp issues JWTs with a role claim (admin, member or guest) and a tenant claim. One internal service decodes the token to read the role, but it never verifies the signature, and it lets the token header choose the algorithm.

Vulnerable check:

const claims = jwt.decode(token);  // no signature check
if (claims.role === "admin") {
  allowAdminAction();
}

Corrected check:

const claims = jwt.verify(token, publicKey, {
  algorithms: ["RS256"],
  issuer: "https://auth.acme-corp.example",
  audience: "acme-billing",
});
const user = await Users.get(claims.sub);
if (user.tenantId !== claims.tenant) throw new Forbidden();
if (user.roleIn(claims.tenant) !== "admin") throw new Forbidden();
allowAdminAction();

The fix verifies the signature with a pinned algorithm, checks issuer and audience, and confirms the role against current server-side data rather than trusting the claim alone.

Why automated scanners often miss it

Scanners may flag obvious issues, such as a token with no signature, but most JWT bugs depend on context. Which services share keys? Which audience should each service accept? Is a role claim the final word, or should it be checked against the database? Does logout actually end access? These are design questions. Answering them means comparing behavior across services and roles over time, which single-pass scanning does not do.

How to prevent it

  • Always verify signatures. Never use a decode-only function for authorization.
  • Pin the accepted algorithm list on the server. Reject "none".
  • Validate expiry, not-before, issuer and audience on every request.
  • Use strong, rotated keys and keep private keys out of source code.
  • Keep access tokens short-lived; store refresh tokens server-side.
  • For sensitive actions, confirm role and tenant against current server data.
  • Use a well-maintained library instead of writing your own parsing.

How to test your own app for it

Only test software you own or have written permission to test.

  • List every service that accepts JWTs and confirm each verifies the signature.
  • Confirm each service pins its algorithm, issuer and audience.
  • Confirm expired tokens and tokens for other audiences are rejected.
  • Confirm role or tenant changes take effect before the token expires where required.
  • Search code for decode calls used in authorization paths.

How Wakefield helps

Wakefield's whitebox agents read your source on the default branch and on every pull request, trace risky paths, comment inline and offer a patch with a confidence level. A separate verifier replays each finding before it reaches you. See Whitebox testing.

Questions

Is JWT insecure by design?

No. JWT is a format. Problems come from how apps verify, scope and expire tokens, so careful configuration matters.

Should I store JWTs in local storage?

Local storage is readable by any script on the page, so a cross-site scripting bug can steal tokens. Many teams prefer secure, HttpOnly cookies for browser sessions.

How do I revoke a JWT before it expires?

Keep access tokens short-lived and check a revocation list or session record for sensitive actions. Refresh tokens should be stored server-side so they can be revoked.

See it on your app.