Password reset is one of those features that looks simple until you draw the whole path. The user enters an email address. The application creates a token. An email service sends an absolute URL. A browser follows the link through DNS, TLS, proxies, redirects, and the reset page. The token is verified. A new password is saved. Existing sessions may or may not remain valid.
Every step is a trust decision. A bug in any one of them can turn account recovery into account takeover or token leakage.
This guide focuses on practical web-application design: building reset URLs from trusted configuration, protecting tokens, avoiding referrer leaks, handling proxies correctly, and making the post-reset session state predictable.
Do not build reset links from untrusted Host headers
An application often needs to generate an absolute link such as:
https://app.example.com/reset-password?token=...
A dangerous shortcut is to construct the origin from the incoming request:
// Risky pattern
const baseUrl = `https://${req.headers.host}`;
const resetUrl = `${baseUrl}/reset-password?token=${token}`;
If an attacker can influence the effective host value that the application trusts, the reset email may point to an attacker-controlled domain while still carrying a valid token.
A safer design uses trusted application configuration:
const PUBLIC_APP_ORIGIN = 'https://app.example.com';
const resetUrl = `${PUBLIC_APP_ORIGIN}/reset-password?token=${encodeURIComponent(token)}`;
The public origin is deployment configuration, not user input.
Reverse proxies make host trust more complicated
Applications behind CDNs and reverse proxies often see forwarded headers such as X-Forwarded-Host or standardized Forwarded values. Those headers can be legitimate signals when inserted by a trusted proxy, but dangerous when the application accepts them directly from the public internet.
The correct setup is architectural:
- only trust forwarding headers from known proxy hops;
- strip or overwrite client-supplied forwarding headers at the edge;
- configure the framework’s trusted-proxy setting correctly;
- avoid using request-derived host values for security-sensitive absolute links where a fixed public origin is available.
Vulnify’s existing reverse proxy security guide covers forwarded-header trust and edge configuration in more detail.
Reset tokens need strong properties
OWASP recommends reset tokens that are random, sufficiently long, stored securely, single-use, and time-limited. Those properties solve different problems.
- Randomness prevents guessing.
- Length makes brute-force search impractical.
- Expiry limits how long a leaked token remains useful.
- Single use prevents replay after a successful reset.
- Secure storage reduces impact if the token database is exposed.
For opaque random tokens, many applications store a cryptographic hash of the token rather than the raw token, similar to the way password reset secrets should not be recoverable from the database unnecessarily.
Do not log the token
Reset tokens can leak through application logs, reverse-proxy logs, analytics, error monitoring, and browser history. Avoid logging complete reset URLs or query strings in places where broad operational staff can access them.
If you need correlation, log an internal reset request ID or a short non-secret reference rather than the credential itself.
Use HTTPS for the entire reset journey
The reset link, form submission, and authenticated account flow should use HTTPS. A redirect from HTTP to HTTPS is helpful for accidental requests, but reset emails should point directly to the HTTPS URL.
Use Vulnify’s SSL/TLS tools to confirm the public reset domain has the expected certificate and transport configuration. The broader Security Headers Analyzer can also help review browser-facing hardening on the live site.
Protect the token from referrer leakage
A reset page often receives the token in the URL. If that page immediately loads third-party analytics, images, support widgets, or external links, the browser’s referrer behavior can matter.
Modern browsers have safer defaults than in the past, but security-sensitive pages should still minimize unnecessary third-party resources and set an appropriate Referrer-Policy. A strict policy for the reset flow can reduce the chance that sensitive URL information is sent elsewhere.
Better still, exchange the URL token for a server-side reset session and remove the secret from the visible URL as early as the application design allows.
A poisoned reset email through forwarded-host trust
An application sits behind a reverse proxy. The backend framework is configured to trust X-Forwarded-Host from every source. The edge proxy forwards the client-supplied header unchanged.
An attacker submits a password reset request for a victim while sending:
X-Forwarded-Host: attacker.example
The backend uses the effective forwarded host when constructing the email URL. The victim receives a legitimate email from the real application, but the link points to https://attacker.example/reset?token=VALID_TOKEN.
If the victim clicks it, the attacker-controlled server receives the token.
The durable fix is not a filter for one host value. The edge must own forwarded-header trust, and the application should generate password reset links from a configured public origin.
Avoid account enumeration
Password reset request forms should not reveal whether an account exists through obvious text or timing differences. A common response is:
If an account exists for that address, a reset message has been sent.
The backend can still perform the real lookup and send email when appropriate. The public response should be reasonably consistent.
Rate limiting and abuse controls are also important so attackers cannot flood a user’s inbox with reset messages.
Do not change account state before token validation
A reset request should not automatically lock the user out or change the account. An attacker who knows someone’s email address should not be able to deny access simply by submitting repeated reset requests.
Apply the password change only after the user presents a valid token and completes the reset.
What should happen after the password changes?
After a successful reset:
- invalidate the reset token;
- store the new password using the application’s approved password-hashing process;
- notify the user that the password changed;
- consider invalidating existing sessions automatically or provide a clear choice;
- require normal authentication rather than automatically creating a privileged session unless the application has deliberately designed that behavior.
OWASP’s forgot-password guidance recommends allowing or automatically performing session invalidation after the reset.
MFA recovery is not the same as password reset
Do not let a password reset silently bypass multifactor authentication recovery rules. Losing a password and losing an MFA factor are different account-recovery problems. If the reset flow also changes MFA enrollment, treat that as a higher-risk recovery process with additional verification.
Email security still matters
Password reset relies on the user’s email account as a side channel in many systems. Secure your sending domain with appropriate SPF, DKIM, and DMARC configuration, and make reset messages easy to recognize without teaching users to trust arbitrary links.
Email authentication does not make a vulnerable reset URL safe, but it makes spoofing the legitimate message harder.
Keep reset pages simple
A password reset page is not a good place for a dozen advertising, analytics, and personalization integrations. Simpler pages reduce referrer and script exposure and are easier to reason about.
At minimum, review third-party resources, CSP, Referrer-Policy, cookie behavior, and redirects on the reset route separately from the marketing home page.
Test the public flow end to end
A useful authorized test should cover:
- existing and non-existing account request responses;
- rate limiting or abuse controls;
- token randomness and expiry behavior;
- single-use enforcement;
- trusted origin in the emailed link;
- HTTPS and certificate behavior;
- redirects before and after the reset;
- referrer policy and third-party resources;
- session invalidation after password change;
- expired and already-used tokens.
Rotate reset infrastructure secrets after relevant incidents
If an incident exposes application configuration, signing keys, email-service credentials, or the database that stores reset-token material, review whether outstanding reset links can still be trusted. Invalidate affected tokens and rotate the secrets that protect them where the design requires it. Do not assume changing user passwords alone closes a compromise of the recovery system itself.
Document how emergency invalidation works before you need it. A recovery feature that has no way to revoke all outstanding reset attempts can turn a small incident into a much harder cleanup exercise.
What Vulnify can help validate
Vulnify cannot inspect how your backend generates reset tokens or prove that a token is random enough from the public surface alone. Those controls require application design and code review.
Vulnify can help with surrounding public conditions: TLS and certificate state, response headers, redirects, cookie posture, and broader website exposure. The Security Headers Analyzer is useful for the live reset page, and the broader website scanner can help identify related public-surface weaknesses after deployment.
Password reset review checklist
- Reset URL built from trusted configuration rather than user-controlled Host data?
- Forwarded headers accepted only from trusted proxy hops?
- Token cryptographically random and sufficiently long?
- Token stored securely?
- Token single-use and time-limited?
- Reset URLs excluded from broad logging?
- HTTPS used directly in emailed links?
- Referrer leakage minimized?
- Non-existent accounts not revealed?
- Abuse controls present?
- Existing sessions handled deliberately after reset?
- MFA recovery kept separate?
Account recovery deserves production-grade design
Password reset is an authentication feature, not a convenience form. Build reset links from trusted origins, treat tokens like temporary credentials, minimize where those credentials can leak, and make session behavior after reset explicit.
The strongest flow is also easy to explain: the user requests recovery, receives a short-lived single-use secret over a trusted channel, lands directly on the expected HTTPS origin, changes the password, and leaves no reusable token or ambiguous session state behind.
