Password reset links that never expire
What it looks like
// reset-password.ts
const token = crypto.randomUUID();
await db.resetTokens.insert({ userId, token }); // no expires_at
await sendEmail(user.email, `${APP_URL}/reset?token=${token}`);
// /reset handler: looks the token up, sets the password,
// and never deletes itWhat happens
An old reset email found in a shared inbox, a forwarded thread or a breached mailbox lets someone take over the account months later.
Where it shows up
Descriptive, not a verdict: builders and stacks whose defaults make this reachable, or that a public incident names. Each hub states the tool's own security features.
Check it yourself
Uses your builder's dashboard and your browser. No tools to install.
- Request a password reset for your own test account and keep the email.
- Use the link to set a new password, then click the same link again.
- Request a second reset, wait a day, then open the day-old link.
- If your app uses Supabase Auth, open Authentication, then Providers, then Email, and read the Email OTP Expiration value.
A bad result is a used or day-old link still letting you set a password, or an expiry set to more than a day.
What we check
We generate several reset links, then replay used, aged and slightly altered tokens against the reset endpoint, and check whether tokens are stored in plain text in the database. The report states which replays succeeded and how the tokens are stored.
Fix direction
Let the auth provider run the reset flow where you can: Supabase's email links are one-time and expire after an hour by default, and its docs discourage settings above a day. If the flow is custom, OWASP's guidance is a random token that is single use, expires after a short period and is stored hashed.
Mechanism documented in: Forgot Password Cheat Sheet (verified 2026-09-21); Passwordless email sign-in (verified 2026-09-21).
Covered by
Pre-launch check ($490, 48 hours) and Security sweep ($290, 24 hours)
Both include a re-check after your fixes.
Related patterns
- Sessions that survive logout and password changesLogin and permissionsHigh
Someone who once had a user's token, on a shared computer or through a leak, keeps access after the user logs out or changes their password.
- Tokens trusted without server verificationLogin and permissionsCritical
Anyone can write their own token naming another user, or an admin, and the server will act as that person.
- Default admin credentials left in placeLogin and permissionsCritical
Anyone who reads the code or guesses a common placeholder can sign in as the administrator.
- No rate limiting on sensitive routesReliabilityHigh
A script can try thousands of passwords against your login form, hammer a form that sends emails or calls a paid API, or knock the app over, and the bill lands on you.
Questions
how long should a password reset link be valid
Long enough to open the email, no longer. OWASP's forgot-password guidance asks for tokens that are single use and expire after an appropriate period; Supabase's default for email links is an hour and it discourages anything above a day. Test your own app by clicking a used link and a day-old link.
does supabase password reset link expire
Yes. Supabase's docs state that the Email OTP Expiration setting governs magic links and other email links, including password recovery, and that links expire after an hour by default and are one-time use. The setting is under Authentication, then Providers, then Email. The risk appears when generated code bypasses Supabase Auth and writes its own reset flow.
can an old password reset email be used to hack an account
If the token never expires and is not deleted after use, yes: anyone who later reads that email can set a new password. Shared inboxes, forwarded threads and mailbox breaches are the usual routes. The self-check above, clicking a used link and an aged link, tells you in a few minutes whether your app has this gap.
Back to the catalogue, or run the self-check for your stack.