What Broken Authentication Means in 2026

OWASP A07:2021 "Identification and Authentication Failures" covers a wide surface: weak passwords, insecure session management, missing rate limiting, predictable tokens, and flaws in credential recovery flows. The common thread is that the mechanisms meant to confirm "this request is from who it says it's from" can be bypassed or abused.

In 2026 the threat model has shifted. Most successful authentication attacks aren't about cracking passwords β€” they're about credential stuffing (using breached credential lists) and session token theft. The brute-force-a-6-character-password scenario is mostly historic; the "login with someone's email and their reused password from a 2019 breach" scenario is the present.

Scale of the problem: Have I Been Pwned has indexed over 14 billion unique credential pairs from public breaches. Attackers have access to the same datasets. If your users reuse passwords (most do), credential stuffing works β€” unless you defend against it.

Credential Stuffing β€” The Automated Threat

Credential stuffing is automated login attempts using breached email/password pairs from other sites. Attackers buy or download credential dumps and run them against your login endpoint with tools like Sentry MBA or OpenBullet. Success rates depend on how many of your users reuse passwords β€” typically 0.1% to 2% of a large list will work.

The defence is multi-layered:

  • Rate limiting by IP and by account β€” slow down automated attempts
  • Bot detection / CAPTCHA on login after failures
  • Breached password checking β€” check new passwords against HaveIBeenPwned's API (k-anonymity model is privacy-safe)
  • MFA β€” even a compromised password doesn't grant access
  • Anomaly detection β€” login from new country/device triggers step-up auth

Weak Session ID Generation

Session IDs must be cryptographically random β€” not predictable from timestamp, incrementing counter, or weak PRNG. A predictable session ID means an attacker can enumerate valid sessions.

session_gen.py Python
import random, time, secrets

# WRONG β€” predictable, enumerable
session_id = str(int(time.time()))
session_id = str(random.randint(100000, 999999))  # only 900k possibilities

# WRONG β€” MD5 of predictable values
session_id = hashlib.md5(f"{user_id}{time.time()}".encode()).hexdigest()

# RIGHT β€” cryptographically secure random bytes
session_id = secrets.token_urlsafe(32)  # 32 bytes = 256 bits of entropy

Session IDs need at least 128 bits of entropy, generated by a CSPRNG. Python's secrets module, Java's SecureRandom, Node's crypto.randomBytes() β€” all appropriate. random.random(), Math.random(), timestamps β€” never for security tokens.

Session Fixation Attacks

Session fixation occurs when an application allows a pre-authentication session ID to remain valid after login. An attacker can set a known session ID on the victim's browser (via a link with the session token in the URL), wait for the victim to log in, and then use that same session ID to access the victim's authenticated session.

The fix is simple: always regenerate the session ID at login. This is something frameworks should handle automatically β€” but custom session implementations frequently forget it.

login_handler.py Python
# Flask-Login handles session regeneration correctly
# Custom session handling must do this explicitly:

def login(username, password):
    user = authenticate(username, password)
    if user:
        # CRITICAL: invalidate old session, create new one after auth
        session.clear()                          # clear pre-auth session
        session["user_id"] = user.id          # set user in new session
        session["session_token"] = secrets.token_urlsafe(32)  # new token
        return redirect("/")

"Remember Me" Done Wrong

Long-lived "remember me" tokens are a common source of session theft. The typical bad implementation stores the user ID + a random token in a cookie and a hash in the database. Problems arise when:

  • The token is predictable (user ID + timestamp)
  • The token is reused after logout (not invalidated)
  • The token doesn't expire
  • All tokens for a user aren't invalidated on password change
  • The token isn't bound to any additional context (IP, UA)

Security breach should invalidate all remember-me tokens: When a user reports suspicious access or changes their password due to a suspected compromise, invalidate all active sessions and remember-me tokens immediately. Most implementations don't do this.

Password Reset Flaws

Password reset flows are a secondary authentication mechanism β€” and often a weaker one than the primary. Common failures:

reset_vulnerable.py Python
import random

# WRONG β€” short numeric token, brute-forceable
token = str(random.randint(100000, 999999))

# WRONG β€” token never expires
db.save_reset_token(user_id, token, expires_at=None)

# WRONG β€” token still valid after use (replayable)
def verify_reset(token):
    user = db.find_by_token(token)
    # Missing: db.invalidate_token(token)
    return user

# RIGHT
from datetime import datetime, timedelta
token = secrets.token_urlsafe(32)              # 256 bits, CSPRNG
expires_at = datetime.utcnow() + timedelta(minutes=15)  # short expiry
db.save_reset_token(user_id, token, expires_at)

def verify_reset(token):
    record = db.find_by_token(token)
    if not record or record.expires_at < datetime.utcnow():
        return None
    db.invalidate_token(token)  # one-time use
    return record.user

Brute Force and Rate Limiting

Login endpoints without rate limiting are vulnerable to brute force and credential stuffing at scale. Basic controls to implement:

  • Per-account lockout after N failed attempts (with lockout notification to user)
  • Per-IP rate limiting β€” limit login attempts per IP per minute/hour
  • Progressive delays β€” exponential backoff after failures
  • CAPTCHA after threshold β€” trigger after 3-5 failed attempts

Account lockout can be abused for DoS: Hard lockout (permanent until manual unlock) can be used to lock out legitimate users. Use progressive delays and soft lockouts with time-based expiry, not permanent blocks triggered by attackers.

Multi-Factor Authentication (MFA) and Its Weaknesses

MFA is the single most effective control against credential stuffing and account takeover. But implementation matters:

  • SMS OTP is weak β€” SIM swapping attacks bypass it. TOTP apps (Authenticator, Authy) are better; hardware keys (FIDO2/WebAuthn) are best.
  • MFA bypass via account recovery β€” if your account recovery flow (email link, backup codes) can be triggered without MFA, it's an MFA bypass.
  • OTP replay attacks β€” TOTP codes should be single-use; track used codes within the current time window.
  • Phishing-resistant MFA β€” TOTP codes can be phished in real-time. FIDO2/WebAuthn is phishing-resistant because the credential is bound to the origin domain.

Modern Auth Patterns That Actually Work

The most reliable path for most applications is to not build authentication yourself:

  • OAuth2 + OIDC with a trusted IdP β€” Auth0, Okta, Cognito, or open-source options like Keycloak. You get session management, MFA, breached password detection, and anomaly detection bundled in.
  • Passkeys (FIDO2/WebAuthn) β€” phishing-resistant, no passwords to breach, seamless UX. Support is now broad enough for mainstream use.
  • JWT best practices β€” if you issue JWTs: use short expiry, require signature verification on every request, use RS256 or ES256 (not HS256 with a weak secret), implement token revocation via a blocklist for critical operations.

Don't build your own auth: Authentication is genuinely hard to get right. The list of things that can go wrong β€” session fixation, timing attacks on token comparison, TOTP window handling, logout invalidation β€” is long. Use proven libraries and services unless you have a very good reason not to.

Find Authentication Flaws in Your Codebase

AquilaX SAST identifies weak session generation, insecure token handling, and missing rate limiting patterns across your authentication code automatically.

Start Free Scan