Password Reset Flows: Five Steps Where Attackers Intercept Them

Password Reset Flows: Five Steps Where Attackers Intercept Them

A password reset flow moves through five distinct backend steps before login succeeds


In Vaadata's documented attack, someone fires three password reset requests, their own account, then the victim's, then their own again, and uses that sequence to brute-force a UUID that's partially predictable from its timestamp component. No password field ever gets touched. Most teams spend their security review time hardening password hashing, but account takeovers actually happen in the five-step reset chain between "Forgot Password" and a working login. So the real question is: which of those five handoffs is your implementation silently trusting without verifying?



  • Step 1, user initiation: the user submits an email or username, and the backend looks up whether an account exists for that identifier.
  • Step 2, token generation: the server creates a reset token, usually a random string or UUID, and stores it with an expiration timestamp tied to the account.
  • Step 3, delivery: the token gets embedded into a URL and sent, almost always by email, sometimes by SMS.
  • Step 4, verification: the user clicks the link, the backend checks the token against what it stored and confirms it has not expired or been used.
  • Step 5, session issuance: on a valid token, the server lets the user set a new password and issues a fresh authenticated session.

Each handoff between these steps, frontend to backend, backend to email provider, email provider to user inbox, token in URL to token in database, is a point where the implementation either verifies the request or just assumes it's fine. That assumption is exactly where the attacks in the next section land.



Attackers target specific gaps in the reset chain, not the login form itself


The five steps above describe how the reset flow is supposed to work. The next question is which of those steps attackers actually go after. Based on the Sentry Security and Vaadata research referenced above, most real-world account takeovers through password reset skip password guessing entirely. Attackers manipulate the reset mechanism itself, often through requests that look completely ordinary in server logs. Vaadata's writeup documents the case already described: someone creates their own account, then fires three password reset requests in sequence, attacker account, then victim account, then attacker account again, specifically to brute-force a UUID that's partially predictable from its timestamp component.



  • Token predictability: UUIDs or reset tokens generated with weak randomness or timestamp-based logic can be reconstructed through brute force, as the Vaadata case against a test account shows (Step 2 above).
  • Account enumeration: Authgear's research found that if the reset form responds differently depending on whether an email exists, attackers can systematically probe addresses to build a list of valid accounts before attempting takeover (Step 1 above).
  • Stale token reuse: Sentry Security's writeup is blunt about this one. Without strict expiration policies, old reset tokens sitting in email histories or server logs stay exploitable long after the user has moved on (Step 4 above).
  • Host header and link manipulation: Vaadata's example describes a request where the attacker alters the Host header to point to a server they control, tricking the backend into generating a reset link outside the intended domain (Step 3 above).
  • PII leakage in reset links: Authgear flags that embedding personally identifiable information directly in reset URLs hands attackers extra leverage if the link gets intercepted through browser history, shared logs, or forwarded email threads (Step 3 above).

None of this requires breaking cryptography. It requires finding the one step, out of five, where the implementation trusted a request without checking it closely enough.



Fixing one weak step matters more than hardening the password field itself


Each gap above maps to one of the five steps, so each gap also maps to a specific fix at that same step. Breach postmortems trace back to the recovery flow far more often than the login form, and yet teams still spend more review time on password hashing than on reset token design. If you're auditing an existing system, or building a new one, walk through the same five steps an attacker would walk through and check each against the findings above.



  • Set short token lifetimes: Authgear recommends reset links expire within an hour, which directly closes the stale-token-reuse gap Sentry Security documented.
  • Generate tokens with full cryptographic randomness: skip timestamp-derived or sequential UUIDs. The Vaadata case proves that partial predictability alone is enough to make brute force practical.
  • Normalize responses regardless of account existence: return the same message whether or not an email is registered. That one change removes the enumeration vector Authgear describes.
  • Validate the Host header and link construction server-side: the manipulated-host trick from Vaadata only works if the backend trusts client-supplied host information when building the reset URL. Don't let it.
  • Keep PII out of the URL itself: Authgear's recommendation to strip identifying data from reset links limits the damage if a link leaks through browser history or forwarded messages.

None of these fixes need a new framework or a rewrite. The question at the start was which of the five handoffs in your implementation is silently trusting an unverified request, and the way to find out is to test, log, and rate-limit each of the five steps separately. The Vaadata case and the gaps above all point to the same lesson: a reset flow fails at one specific handoff, not as a single feature that either works or breaks entirely.