A07 Authentication Failures
Login is the front door. Unlimited guesses, reused passwords, sessions that never expire and tokens that are not really verified all let an attacker walk in as a real user.
What it is
Authentication failures are weaknesses in confirming a user's identity or managing the session that follows: permitting and brute force, accepting weak or breached passwords, exposing session ids, skipping or mis-verifying tokens, or offering a weak recovery flow.
:2025 lists it as A07 under the name Authentication Failures.
How the attack works
- Credential stuffing: lists of leaked e-mail and password pairs from other sites are replayed against your login at scale.
- Brute force and password spraying: a few common passwords across many accounts, below any naive lockout.
- Weak JWT handling: the server accepts alg: none, never checks the signature or the expiry, or uses a guessable secret.
- and theft: the session id is not rotated after login, travels in the URL, or the cookie lacks HttpOnly and Secure.
- Account recovery bypass: reset links that never expire or security questions with public answers.
Vulnerable code
No throttling, a fast comparison of an unsalted hash, a session id kept across login, and a token that is decoded but never verified.
app.post("/login", async (req, res) => {
const user = await db.user.findByEmail(req.body.email);
if (user && user.passwordHash === sha1(req.body.password)) {
req.session.userId = user.id; // same session id as before login
return res.json({ ok: true });
}
res.status(401).json({ error: "wrong email or password" }); // unlimited attempts
});
const claims = jwt.decode(token); // decode() does NOT verify the signatureclaims = jwt.decode(token, options={"verify_signature": False})The fix
Throttle by account and by IP, verify passwords with a slow hash, regenerate the session on login, and set cookie flags. Verify tokens with a pinned algorithm, a real key, and issuer, audience and expiry checks. Where you can, use passkeys or an identity provider instead of building login yourself, and add a second factor for sensitive accounts.
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, limit: 10, keyGenerator: (r) => `${r.ip}:${r.body?.email}` });
app.post("/login", limiter, async (req, res) => {
const user = await db.user.findByEmail(req.body.email);
const ok = user && (await argon2.verify(user.passwordHash, req.body.password));
if (!ok) return res.status(401).json({ error: "wrong email or password" }); // same message for both cases
req.session.regenerate(() => { // new id after login: defeats fixation
req.session.userId = user.id;
res.json({ ok: true });
});
});
app.use(session({ cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 3600_000 } }));claims = jwt.decode(
token,
key=PUBLIC_KEY,
algorithms=["RS256"], # pin the algorithm; never accept "none"
audience="api.example.com",
issuer="https://id.example.com",
options={"require": ["exp", "iat", "sub"]},
)http.sessionManagement(s -> s.sessionFixation().migrateSession())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())); // verifies signature, exp, issuer via configProve the fix
Test the behaviors an attacker relies on: the eleventh attempt is refused, the session id changes after login, and tampered or unsigned tokens are rejected.
def test_login_is_throttled(client):
codes = [client.post("/login", json={"email": "a@x.io", "password": f"guess{i}"}).status_code for i in range(15)]
assert 429 in codes
def test_session_id_changes_after_login(client, user):
before = client.get("/").cookies.get("sid")
client.post("/login", json=user.credentials)
assert client.cookies.get("sid") != before
def test_unsigned_token_is_rejected(client):
forged = b64(b'{"alg":"none"}') + "." + b64(b'{"sub":"admin"}') + "."
assert client.get("/api/me", headers={"Authorization": f"Bearer {forged}"}).status_code == 401
def test_cookie_flags(client, user):
cookie = client.post("/login", json=user.credentials).headers["set-cookie"]
assert "HttpOnly" in cookie and "Secure" in cookieChecklist
- Rate limit and monitor login by account, by IP and by device; add progressive delay or challenge, not just hard lockout (which enables denial of service).
- Check new passwords against a breached-password list; require length over arbitrary complexity rules.
- Offer multi-factor authentication, ideally phishing-resistant (passkeys, ), and require it for admins.
- Rotate the session id on login and privilege change; expire it; invalidate it on logout.
- Verify every token: signature, algorithm, issuer, audience, expiry. Never trust decode alone.
- Make recovery as strong as login: single-use, short-lived links; same generic response for known and unknown e-mails.
connections on the map
- A06 Insecure Designsets the recovery flow ofA07 Authentication Failures
- A01 Broken Access Controlneeds a trusted identity fromA07 Authentication Failures
- A04 Cryptographic Failuresprotects credentials ofA07 Authentication Failures
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- Bot managementsecopscredential stuffing is, first of all, automated traffic
Unlocks
test what you learned
Loading questions…