Back to Cheat Sheets

๐Ÿ”‘ Authentication FailuresOWASP 2025

OWASP Web Top 10 2025 ยท A07

CRITICAL RISK

๐Ÿ“‹ What Is It?

Authentication Failures covers every way an application fails to reliably confirm who is making a request and to keep that confirmation trustworthy for the life of a session. It is the 2025 evolution of A07:2021 Identification and Authentication Failures (itself the 2017 "Broken Authentication"), broadened to today's reality: passkeys, federated sign-in, and short-lived tokens. At its core, a failure occurs whenever an attacker can obtain, guess, forge, or reuse proof of identity that belongs to someone else โ€” leading directly to account takeover.

A07OWASP 2025 Rank
CWE-287Core: Improper Authentication
Since '17In Top 10 every edition

โš ๏ธ Top Attack Vectors

  • Credential stuffing / spraying: replay breached password pairs, or one common password against many accounts (CWE-307).
  • Account enumeration: different message, status, or timing reveals which accounts exist.
  • Session fixation / weak IDs: ID not rotated on login, or low-entropy / predictable (CWE-384).
  • MFA fatigue & AitM relay: push-bombing, or a reverse-proxy phishing kit relaying OTPs to steal the session cookie.
  • Weak reset flows: guessable / non-expiring tokens, or host-header link poisoning (CWE-640).
  • JWT / OAuth flaws: alg:none, algorithm confusion, missing exp/iss/aud, loose redirect_uri, missing state/PKCE.

๐Ÿ”ด Attack Flow

1. Enumerate valid usernames/emails
โ†“
2. Acquire creds (stuff, spray, or AitM phish)
โ†“
3. Bypass second factor (fatigue, SIM-swap, OTP relay)
โ†“
4. Take over the session (fixation, stolen cookie, forged JWT)
โ†“
5. PERSIST: long-lived token + attacker MFA device

โŒ Vulnerable Code

// Node/Express โ€” enumerable, no rotation, no throttle, no MFA app.post('/login', async (req, res) => { const user = await db.user.findByEmail(req.body.email); if (!user) return res.status(404).json({ error: 'No account with that email' }); // leaks existence const ok = await bcrypt.compare(req.body.password, user.hash); if (!ok) return res.status(401).json({ error: 'Incorrect password' }); // leaks existence req.session.userId = user.id; // no session rotation -> fixation res.json({ token: user.id }); // predictable "token" });

โœ… Secure Code

// Uniform response + constant work, rotate the session, gate on MFA const DUMMY_HASH = '$2b$12$....placeholder....'; // so unknown users cost the same app.post('/login', loginLimiter, accountThrottle, async (req, res) => { const user = await db.user.findByEmail(String(req.body.email).toLowerCase()); const ok = await bcrypt.compare(req.body.password, user ? user.hash : DUMMY_HASH); if (!user || !ok) return res.status(401).json({ error: 'Invalid email or password' }); // uniform if (user.mfaEnabled) { req.session.pendingUserId = user.id; return res.json({ mfaRequired: true }); } req.session.regenerate(() => { // NEW id kills fixation req.session.userId = user.id; req.session.save(() => res.json({ ok: true })); }); });

โœ“ Prevention Checklist

  • Favor length over composition; screen against breach corpora (HIBP k-anonymity)
  • Hash with Argon2id / scrypt / bcrypt โ€” never fast/unsalted
  • Throttle per-account and per-source; step-up CAPTCHA over naive lockout
  • Enforce MFA on sensitive actions; prefer phishing-resistant passkeys/WebAuthn
  • CSPRNG session IDs; rotate on login/privilege change; invalidate server-side on logout
  • Cookies: HttpOnly, Secure, SameSite, idle + absolute timeout
  • Reset tokens: hashed, single-use, short expiry; build links from a trusted origin
  • Pin the JWT algorithm; validate exp/iss/aud; exact redirect_uri + state + PKCE

๐Ÿ” Detection & Tools

Burp Suite Hydra OWASP ZAP jwt_tool HaveIBeenPwned API WebAuthn / passkeys

Quick Tips:

  • DO NOT return distinct messages/timing for unknown vs. wrong-password
  • DO NOT accept alg:none or let the token pick its algorithm
  • DO rotate the session ID at every privilege boundary
  • DO move privileged users to phishing-resistant MFA
Key Takeaway: The attack vectors chain (enumerate โ†’ spray โ†’ stuff โ†’ bypass MFA โ†’ hijack session โ†’ persist), so defenses must be layered. Closing enumeration alone, or adding phishable MFA alone, still leaves a viable path to account takeover.
Myth to drop: "We have MFA, so we're safe." SMS and TOTP are phishable โ€” an adversary-in-the-middle page relays the code in real time. Only origin-bound WebAuthn/passkeys defeat it.