Skip to content
ShipSecurelySecurity reference
Menu
Your tool here — sponsor ShipSecurely

Issues

Weak password reset

A reset flow that uses a short code, a guessable link, or a client-only check lets someone take the account without the password.

Share link · Share on X

Last updated

highAccess control

Shows up in: Supabase, Firebase, Next.js

Markdown version

What it is

Password reset is account takeover if the token is weak. The safe shape is the one your auth provider already built: a long, random, single-use token, emailed as an HTTPS link, checked on the server, expired quickly.

The flows agents invent are worse. A 4-digit code with no limit. A link that puts the new password in the query string. A page that asks for the email and the new password and updates the row if the email exists, with no token at all. A token stored in localStorage by the client and trusted later.

Telling the user "if an account exists, we sent mail" versus "no account for that email" is a smaller issue. It lets someone probe which emails are signed up. Use the same response either way. It does not replace a real token.

Supabase Auth and Firebase Auth both provide password reset email. Use those instead of a homemade code unless you have read the provider's docs and are implementing their token exchange.

How to check in 5 minutes

  1. Find the reset request route and the page that sets the new password. There should be a token from the email between them. If the update only needs the email address, it is broken.

  2. Check the token. It should be long and random, not the user id, not the timestamp, not a 4-digit PIN.

  3. Request two resets. Use the older link after the newer one, and use the same link twice. A single-use token fails the second time.

  4. Confirm the route that sends mail is rate limited. See no rate limiting. Confirm the new password is not logged.

How to fix

Supabase

Use Supabase Auth's reset email from the client or server, with a redirectTo URL on an allowlist you configured in the Auth settings. The redirect page should exchange the token through the Supabase client session flow, then call the password update on that session. Do not write a custom update users set password from a service-role query that trusts an email field in the body.

Confirm the redirect URL allowlist does not contain *. An open redirect on the reset link can send the token to another site. Keep the allowlist to your real origins.

Firebase

Use sendPasswordResetEmail from the Firebase client SDK, or the Admin SDK's generate-link API if you send the mail yourself. If you send it yourself, the link still has to be the one Firebase generated. Do not invent a second code and store it in Firestore with rules that let anyone read the codes collection.

Next.js

If you implement reset yourself because you hash passwords in your own table:

  • Generate the token with a cryptographic random source, at least 32 bytes, and store only a hash of it.
  • Expire it in an hour or less.
  • Mark it used when the password changes.
  • Require the raw token to set the password. Ignore a request that only has the email.
  • Rate limit both the request and the confirm route.

Do not put the token in a log, an analytics event, or a referrer to a third-party script on the reset page. That page should be simple.

Vercel

No host setting fixes a bad reset route. Do not put the token in a rewrite or a query parameter you then send to an analytics domain.

Copy-paste prompt

Paste this into Cursor, Claude Code, or whichever agent is editing the repo. Read the diff before you merge it.

You are fixing password reset. Do not add features.

Find the reset request and the set-new-password path.

1. If this project uses Supabase Auth or Firebase Auth, switch the flow to that provider's reset email. Delete homemade 4-digit codes.
2. If passwords live in our own database, the token must be at least 32 random bytes, stored only as a hash, single-use, and expired within an hour. The update must require the token, not only the email.
3. Return the same message whether or not the email is registered.
4. Add a server-side rate limit on the send route.
5. Make sure reset redirect URLs are an allowlist, not a wildcard.
6. Do not log the token or the new password.

Show the flow in a few sentences and the files you changed.

Questions

Can the user stay logged in on other devices after a reset?

That is a product choice. Invalidating other sessions on password change is the safer default, because the reset may be happening because those sessions were stolen. Supabase and Firebase have their own session behavior. Read their current docs before you promise a global sign-out you did not implement.

Is email enumeration worth fixing if the token is strong?

Yes, as a small fix: one response text either way. It is not a substitute for the token, the expiry, or the rate limit.

Sources

Related