Password Reset Security: Protect Recovery Without Locking Out Users

A secure password reset is an account-recovery workflow, not just an email with a link. Give the same public response for known and unknown accounts, limit repeated requests, send a short-lived single-use token to a trusted address, verify it before changing the password, and notify the account owner after the change. Test the entire journey, including expired links, stolen sessions and people who no longer control their email.
Separate a reset request from an account change
An unexpected reset email means someone submitted an identifier or initiated a recovery flow. It does not, by itself, show that the requester opened the message, used the token or changed the password. The distinction matters when the support team decides what to investigate and what to tell the account owner.
Record request, token validation, completed reset and subsequent sign-in as separate events. Include time, outcome and a privacy-conscious request identifier. Avoid putting the reset token, full recovery URL or new password in application logs, analytics events and support tickets. Give staff enough evidence to investigate repeated requests without turning the audit trail into a second source of credentials.
Design the request step against enumeration and email flooding
The public form should accept the account identifier and return one neutral confirmation, such as “If an account matches, we will send recovery instructions.” The OWASP Forgot Password Cheat Sheet recommends consistent responses and timing for existing and nonexistent accounts. Use the same visible result and comparable processing path whether the account exists or not.
Apply controls at more than one level. A per-account limit reduces the number of messages sent to one person; a per-origin or network limit slows broad automated submissions. Monitor delivery volume and blocked attempts. A global limit alone leaves a single account open to flooding, while an account-only limit leaves room for a broad sweep. Add a challenge only when the threat and accessibility cost justify it, then test that real users still reach recovery.
Do not disable the account merely because a reset was requested. A stranger should not be able to lock out a customer by entering their email address repeatedly. Protect the request endpoint, but keep normal sign-in available until a valid recovery step changes the account.
Treat the token as a credential
A recovery link grants a narrowly defined ability to set a new password. Generate its token with a cryptographically secure source, bind it to one account, store it securely, give it a limited lifetime and invalidate it after successful use. OWASP also recommends protecting the token-entry endpoint against guessing. Make the token purpose-specific: a password-reset token should not authorize ordinary account access or unrelated profile changes.
Build the link from a configured, trusted site origin instead of accepting the request’s Host header as the destination. Serve the flow over HTTPS. Set a restrictive referrer policy on the reset page so third-party requests do not receive a recovery URL through the Referer header. Keep analytics, advertising tags, chat widgets and unnecessary external assets away from the token-bearing page. If the link opens a form, move the secret out of the visible URL as soon as the server has validated it and established a limited recovery session.
Show a clear expired-or-used state with a safe route to request a fresh link. Do not reveal whether an account exists in that error path. A new request should not leave an older token usable indefinitely; define and test the intended invalidation behaviour rather than assuming the mail service or CMS handles it.
Complete the reset as a controlled transaction
After the token is verified, let the person set and confirm a new password under the same password rules used at registration. Store only the server-side password hash. Do not email the new password, automatically sign the person in or redirect to an arbitrary return URL. OWASP recommends a normal sign-in after the change and a separate notification that the password was reset.
Decide what happens to existing sessions. For higher-risk accounts, invalidate them after the password changes. For other products, offer the user a clear choice and document the trade-off. A stolen session is not made safe merely because the password changed. Server-side session invalidation matters because clearing only the browser cookie does not revoke a stolen session elsewhere.
Recovery for a lost second factor is a different, sensitive workflow. Do not silently bypass MFA because the email reset succeeded. Define how the person proves control of the account, what evidence support staff need and how the account owner is notified. The OWASP MFA guidance treats factor reset as an attack target of its own.
What to check on a WordPress site
WordPress sends a reset link to the address associated with the account, as its administration guide describes. The core reset-key validation function checks the login and key and has a default one-day expiration filter. Those core behaviours do not prove that every installed login, security, caching or email plugin preserves the intended flow.
Map the actual routes for request, email link, token validation and final sign-in. If a site uses a custom login URL, test the full email link in a clean browser session. A redirect rule that hides the standard login page can also interrupt recovery. Check that the recovery page is not cached, that the email points to the correct host and that a link cannot be replayed after use. Do not solve reset-email abuse by changing only the visible login slug: the recovery endpoint and request limits need their own controls.
Keep administrative recovery distinct from customer self-service. A small editorial site with only a few staff accounts has different exposure and support needs from a customer portal with many users. Decide whether public self-service is required, which accounts are eligible and how administrators regain access if the mail channel fails. WordPress exposes an allow_password_reset filter, but changing eligibility is a product and operations decision that needs an alternative recovery route.
Run the release test with real failure cases
- Request recovery for a known account and an unknown identifier; compare the public response and timing.
- Repeat requests from one account and from several origins; verify limits without blocking legitimate recovery.
- Open the email link on another device; check host, HTTPS, referrer behaviour and token leakage in page requests.
- Use an expired link, a malformed token and a token twice; each must fail safely.
- Complete a reset, then test old password, new password, active sessions and MFA behaviour.
- Check delivery failure, delayed email, support escalation and the notification after a completed change.
- Review logs and analytics to ensure no token, password or full recovery link was stored.
Run these tests after changes to the login route, security plugin, mail provider, cache, domain or user-account model. They reveal broken recovery paths that a successful homepage check misses.
How VOWE approaches account recovery
Can VOWE build a secure password-reset flow for a web application?
VOWE can include recovery requirements, account roles, integration points and release checks in a Web Application Development project. The exact recovery methods and controls are defined from the users, risk profile and existing authentication architecture of the product.
What if an existing website sends unwanted reset emails?
Website Maintenance is the relevant service for reviewing the live request path, plugin interactions, mail delivery, logging and recovery tests. Repeated emails are assessed separately from completed password changes.
What should a business send for a recovery-flow review?
Describe the account types, sign-in and reset routes, mail provider, MFA setup, recent changes and examples of unwanted messages with secret links removed. Send the details through VOWE Contacts so the review starts from the actual workflow.