← OWASP Top 10:2025

A07:2025

Authentication Failures

Proving who you are, done badly: JWTs decoded but not verified, sessions in localStorage, no brute-force protection, weak password rules, tokens that never expire.

What it is

Authentication is confirming identity: the login, the session, the token. Failures here let someone become another user without their password. Verifying a JWT's structure but not its signature. Accepting a token signed with alg: none. Storing the session token where any script can read it. Letting an attacker try passwords forever. Sessions that survive logout or never expire.

It was renamed from Identification and Authentication Failures in 2025 and holds at seventh. It stays on the list because the primitives are subtle and the libraries have footguns.

How it shows up in AI-generated code

The AI-specific failure is jwt.decode where jwt.verify was needed. Both return the payload; only one checks the signature. A model that has seen thousands of examples of each will pick decode when the goal is "get the user ID from the token", and the result is an app where anyone can mint a token for any user. Prbl has a dedicated rule for it and it appears in our scans in every model cohort.

The other recurring ones: JWTs stored in localStorage because the tutorial did it, session cookies without HttpOnly and Secure, the algorithm left unpinned so a token signed with none or with the public key as an HMAC secret is accepted, and no lockout or rate limit on the login route, which overlaps with insecure design.

Example: Decoded, not verified

The pattern
import jwt from "jsonwebtoken";
const payload = jwt.decode(token);          // no signature check
const user = await db.user.find(payload.sub); // trusts a forgeable claim
The fix
const payload = jwt.verify(token, process.env.JWT_SECRET!, {
  algorithms: ["HS256"],                     // pin the algorithm
  issuer: "app.example.com",
});
const user = await db.user.find(payload.sub);

verify with a pinned algorithm list closes both the missing-signature bug and the algorithm-confusion bug in one call. Never read a claim from decode for anything that grants access.

How to find it

  • Search for jwt.decode and any verify call without an algorithms option.
  • Check where the session token is stored in the browser. localStorage or a cookie without HttpOnly is a finding.
  • Try ten wrong passwords in a row. If nothing slows you down, there is no brute-force protection.
  • Check that logout invalidates the session server-side and that tokens carry an expiry.
  • Prbl rules PRBL-A002 and PRBL-C002 cover unverified JWTs and hardcoded signing secrets; the fix guides cover storage and cookies.

How to fix it

  • Always verify, never only decode. Pin the algorithm.
  • Keep the session in an HttpOnly, Secure, SameSite cookie, not in localStorage.
  • Rate limit and lock out on login, per account and per IP.
  • Short-lived access tokens, server-side revocation for sessions, real logout.
  • Hash passwords with bcrypt, scrypt or argon2; enforce a minimum length; check against breached-password lists.

What Prbl checks for this category

PRBL-A002 JWT decoded without verificationPRBL-C002 Hardcoded session or signing secretPRBL-R001 Weak randomness (tokens)

Run a free scan on a public repo or a live URL. Findings link to the fix guides below.

Fix guides for this category

Go deeper

Common questions

What is the difference between jwt.decode and jwt.verify?

decode reads the payload without checking the signature, so anything anyone typed into a token is trusted. verify checks the signature against your secret or public key and rejects forgeries. If the result is used to grant access, it must be verify.

Is localStorage really unsafe for tokens?

Any script running on the page can read it, so one cross-site scripting bug hands over every user's session. An HttpOnly cookie is invisible to scripts. Cookies need CSRF protection instead, which is a smaller problem than XSS.

Should I use a managed auth provider instead?

For most AI-built apps, yes. Supabase Auth, Clerk, Auth0 and NextAuth get the primitives right. The failures then move to the authorization layer, which is A01, and to configuration, which is A02.

Is this category already in something you shipped? Scan a live URL or a public repo free, no account.

Scan my app →
A07:2025 Authentication Failures Explained: JWT, Sessions and Login in AI-Generated Code | Prbl