All insights
Article · 10 min

The Authentication Mistakes That Quietly Become Security Incidents

Never-expiring tokens, soft password resets, and missing rate limits don't look dangerous in a demo. Here's how each one actually gets exploited, and what a secure auth system requires instead.

Hasnain Ahmed KhanSystems Architect ·
  • Authentication
  • Security
  • Backend

The Authentication Mistakes That Quietly Become Security Incidents

Most authentication systems don't fail in an obvious way. Nobody wakes up to a screaming alert that says "your login flow is broken." Instead, someone forwards a support ticket about a user whose account was accessed from a country they've never visited, or a customer reports they got a password reset email they never requested, and now there's an incident to investigate, a disclosure decision to make, and a very uncomfortable conversation with leadership about how this happened.

The uncomfortable truth is that almost every auth-related breach traces back to one of a small, well-known set of mistakes. They're not exotic. They're the kind of shortcuts that get taken under deadline pressure, work fine in every demo and every QA pass, and only show their teeth when a real attacker - or just an automated bot - starts probing the system at scale.

Tokens that never expire, or expire too generously

A JWT with no expiration, or one set to 30 days "so users don't have to log in as often," is a liability with a long fuse. If that token is ever leaked - through a logged request, a browser extension, a compromised device, or a client-side XSS bug - the attacker doesn't get five minutes of access, they get a month. And because most systems don't have a clean way to revoke a single JWT before it expires (that's the whole point of a stateless token), you're often left rotating signing secrets for everyone just to lock out one attacker.

The fix isn't complicated in concept: short-lived access tokens (minutes, not weeks) paired with refresh tokens that are stored server-side and can actually be revoked. The complexity is in doing this without making the user experience worse - silent refresh flows, proper token rotation on refresh, and detection when a refresh token is used from two different places at once (a strong signal it's been stolen).

Password reset flows that are secure in theory, broken in practice

Password reset is one of the most commonly under-engineered parts of an auth system, because it's treated as a "nice to have" feature rather than a security-critical one. Common versions of this mistake:

  • Reset tokens that don't expire, or expire after days instead of minutes, meaning an old reset email sitting in someone's inbox (or a shared/public inbox) is a live account-takeover vector indefinitely.
  • Reset tokens that are predictable or sequential rather than cryptographically random, making them guessable with enough attempts.
  • No invalidation of the old session when a password is reset. If an attacker has already gained access to an account, resetting the password should kick out every other active session. Many systems don't do this, so the legitimate user "fixes" their account while the attacker stays logged in.
  • Reset flows that leak whether an email exists in the system ("no account found with that email" versus a generic message), which sounds minor but is exactly the kind of information that makes credential-stuffing and targeted phishing campaigns more effective.
  • No rate limit on reset requests, letting an attacker spam a target's inbox with reset emails as a harassment or social-engineering tactic, or attempt to enumerate valid accounts by watching response timing.

A reset flow that actually holds up treats the reset token like a short-lived credential in its own right: single-use, expires in 15-30 minutes, invalidates all other sessions on successful reset, and gives no information about whether an email address exists in the system either way.

No rate limiting on login attempts

This is the one that seems obvious in hindsight but is missing more often than you'd expect, especially in systems built quickly or by teams focused on features rather than security. Without rate limiting, a login endpoint is an open invitation for credential stuffing - attackers running lists of leaked email/password pairs from other breaches against your login form, betting (correctly, depressingly often) that some percentage of your users reused a password that was compromised elsewhere.

Rate limiting needs to happen at more than one layer to actually work: per-IP limits to slow down single-source attacks, per-account limits to prevent one username from being hammered from a botnet of IPs, and ideally some form of progressive backoff or CAPTCHA challenge once suspicious patterns show up. A flat "5 attempts then lock the account for an hour" rule is better than nothing, but it also creates a denial-of-service vector where an attacker locks out a real user just by trying their email with wrong passwords - so the real answer usually involves risk-based throttling rather than a single hard rule.

Weak or absent MFA, and MFA that can be bypassed

Multi-factor authentication gets treated as a checkbox feature in a lot of systems - it exists, technically, but the implementation has gaps. The most common ones:

  1. MFA that can be skipped via a "remember this device" cookie with no real expiration or device fingerprinting. An attacker who steals that cookie inherits the bypass permanently.
  2. SMS-based MFA as the only option, which is vulnerable to SIM-swapping and shouldn't be the sole second factor for anything sensitive - it's better than nothing, but pairing it with (or replacing it with) TOTP or passkeys closes a real gap.
  3. No MFA enforcement on high-risk actions, only on login. Changing a password, adding a new payment method, or changing the account's recovery email are all moments where re-verifying identity matters just as much as login does, and a lot of systems don't ask for it.
  4. Backup/recovery codes that are generated once, shown once, and never rotated or monitored for use, meaning a leaked backup code becomes a permanent skeleton key.

Session management that doesn't hold up

Beyond tokens specifically, general session hygiene is where a lot of smaller mistakes accumulate: sessions that don't get invalidated on logout (the token is just discarded client-side but still valid server-side if presented again), no visibility for users into their own active sessions and devices, no way to remotely revoke a session from a lost or stolen device, and sensitive session data stored in a way that's readable if the storage layer is ever compromised (unencrypted session stores, tokens logged in plaintext for debugging).

Storing and handling passwords incorrectly

This one is less common than it used to be, but still shows up in legacy systems or ones inherited from an earlier, less careful build: passwords hashed with fast algorithms like plain SHA-256 instead of a purpose-built slow hash like bcrypt or argon2, no per-password salt, or - the worst version, still found more often than it should be - passwords logged in plaintext somewhere in an error tracker or application log because an exception handler dumped the full request body.

What a secure auth system actually requires

None of these fixes individually are exotic engineering. What's hard is that auth touches every part of the product, so fixing it properly means auditing token lifetimes, session storage, reset flows, rate limiting, and MFA enforcement together, as one coherent system, rather than patching whichever piece just caused the latest incident. A secure authentication system engagement is exactly this: a full audit of how identity, sessions, and credentials are handled across the app, followed by fixing the highest-risk gaps first - usually token expiration and reset flow issues, since those are the ones most commonly actively exploited - and building toward a system where a single leaked credential or token doesn't cascade into a full account takeover.

If your auth system sits in front of a broader backend that also needs attention - unbounded database access, missing rate limits at the API gateway level, services that assume authentication was already handled upstream - it's worth looking at this alongside a broader scalable backend architecture review, since auth gaps and backend architecture gaps tend to be discovered by the same kind of audit and often share root causes.

Working on something similar?

I write these from real client work. If you're facing the same problem, it's usually faster to just talk it through.