Skip to content

A User Clicks “Forgot Password.” What Must Your App Get Right?

Ask about this article: Claude ChatGPT Grok

You have a sign-in form. Underneath it sits a small “Forgot password?” link. The first version seems manageable: ask for an email address, send a link, let the person choose a new password. You can sketch the screens in a few minutes.

Then someone asks what happens if the person opening that link isn't the account owner. Or if they open it twice. Or if the account is already signed in on a stolen laptop.

Those questions belong in the first version, too.

Start with what the email proves

Ask:

Imagine Mira uses your app to manage bookings for a small studio. She hasn't signed in for a month and can't remember her password. She does still have access to her email.

Your app sends her a link containing a long, unpredictable secret. That secret is usually called a reset token. When she brings it back to the app, the app can authorize a password change for the account it belongs to.

The link is powerful because possession is the evidence. Someone who obtains it may be able to use it without knowing the old password. That's why a support person should never ask Mira to forward the message or paste the link into a chat. She should use it herself.

This also explains the limits of email recovery. It relies on the security of the mailbox. If Mira has lost both her password and access to that mailbox, sending another email doesn't provide a new way to establish ownership.

The form has another audience

Ask:

Now imagine someone submits a list of email addresses to your reset form.

If it says “Email sent” for Mira and “No account found” for everyone else, it has become a way to discover your customers. Security engineers call this account enumeration. The person making the requests doesn't need access to any mailbox to learn something from the answers.

The usual response is deliberately uninformative: if the address is eligible, recovery instructions will arrive. The behavior around that message matters as well. A much faster response for an unknown address can disclose the same information. Repeated requests also need limits so that your recovery form doesn't become a tool for flooding somebody's inbox. These are among the recommendations in OWASP's password-reset guidance.

In SigID's reset implementation, token generation and the recipient cooldown happen before the account lookup. An unknown account follows the generic success path. The service also applies a configurable timing floor and jitter; those are measures to reduce timing disclosure, not a claim that every request will take precisely the same time.

For Mira, this has a usability cost. A typo doesn't get a helpful “wrong email” message. The page has to explain what to check without revealing whether the address belongs to someone else.

Ask:

Mira finds the email on her phone, then opens it again on her laptop. Both devices show a password form.

There are now two requests that could try to use the same permission. Consider an implementation that checks the token, changes the password, and then marks the token as used:

Moment Phone Laptop
1 Checks the link: unused
2 Checks the link: unused
3 Saves one password
4 Saves another password

Both requests passed the check. Mira may have no idea which password won.

Preventing this requires the permission to be consumed as part of the same database operation that replaces the password. A database transaction groups the changes so they succeed together or leave neither committed. The token consumption must also prevent a competing request from claiming the same token.

SigID performs token-bound password replacement inside a transaction. It also checks that the person still has an active membership in the tenant where the reset began. An old email shouldn't reopen a membership that was removed while the message sat in an inbox.

The page can check whether a link appears usable before showing the form. That makes the interface friendlier, but the final submission must check again. Something may have changed between those two requests.

A new password doesn't settle every open session

Ask:

Suppose Mira is resetting her password because her laptop was stolen.

The laptop may already be signed in. Apps usually remember a successful login with a session, allowing subsequent requests without asking for the password each time. Replacing the password doesn't, by itself, tell every session to end.

SigID's password-reset flow supports revoking existing global human sessions in the same transaction as the reset when that option is configured. Your application still has to account for any session of its own. A separate app cookie or an API that accepts signed access tokens locally needs an explicit invalidation or expiry strategy; changing the identity provider's records doesn't magically change those local decisions.

That's a product decision worth making before a user reports a stolen device. What does your “sign out everywhere” promise cover? When does the next request from the old laptop stop working? Test that promise from the laptop's point of view.

What if she has also lost her second factor?

Ask:

Mira may use an authenticator app as well as a password. The extra code is a second piece of evidence required at sign-in.

Resetting the password should not silently remove that requirement. Otherwise, access to her email could become a shortcut around the protection she enabled. Losing the second factor needs its own recovery path, with evidence appropriate to the account. OWASP treats MFA recovery separately from password reset.

SigID's authentication specification keeps credential recovery separate from normal sign-in: recovery does not mint authentication assurance simply because the person completed it. They must authenticate using the available methods afterward. The options shown to a user depend on the account and application policy.

Support needs to understand this distinction as well. “I can change your password” and “I can remove every obstacle to signing in” are very different promises.

The part you can hand over

Ask:

By now the original three screens have acquired some substantial obligations: protect account privacy, deliver a secret, consume it safely, preserve the account's authentication requirements, and handle access that already exists. There are also ordinary support problems: expired messages, mistyped addresses, and people trying to recover through the wrong application.

These are good reasons to use hosted identity. With SigID, password recovery belongs to the hosted sign-in flow, alongside the credential and session services it depends on. You integrate that flow instead of maintaining another password database and a separate reset mechanism in your app.

You still own your application's permissions, local sessions, and support instructions. Start with adding SigID login, then walk through recovery using a test account that is already signed in on a second device. The behavior on that second device will tell you more than a successful “Password changed” screen.