Authentication
Password Reset
Lets users recover access when they forget their password, via a time-limited email link to set a new one.
When to use this
Required for any app that uses password authentication. Without it, forgotten passwords mean lost users.
What I assumed
I made these guesses to fill gaps. Let me know if any are wrong.
Flow diagram
Step-by-step recipe
Copy this and paste into Cursor, Claude Code, or v0.
PATTERN: Password Reset
INPUT: email (in request), new_password (in reset)
OUTPUT: password_updated | error_message
STEPS:
1. User clicks "Forgot password?" on sign-in screen
2. User enters email address
3. ALWAYS show "If that email exists, we've sent a reset link" โ never confirm/deny existence (security)
4. IF email exists in DB โ generate one-time signed token (1h expiry)
5. Send reset email with link containing token
6. User clicks link in email
7. Server validates token: not expired, not used, hash matches
8. IF invalid โ show "This link expired. Request a new one?"
9. Show new password form with strength meter
10. User enters new password (min 8 chars, ideally with strength check)
11. Hash and store new password (bcrypt/argon2)
12. Mark token used, invalidate ALL existing sessions for this user
13. Send confirmation email "Your password was changed"
14. Redirect to sign-in with success toast
ERROR_HANDLING:
- Email service down โ log + show "Try again shortly", don't expose service status
- Token expired โ friendly "Link expired, request a new one" with CTA
- User submits weak password โ show specific guidance (length, complexity)
- Concurrent reset requests โ invalidate previous tokens, only latest valid
EXTENSION_POINTS:
- 2FA challenge after reset (composable_with: ["2fa"])
- Notify on suspicious reset (different IP/country) (composable_with: ["security-notification"])
- Compromise check via HaveIBeenPwned (composable_with: ["password-breach-check"])
States โ how things change
| State | Description | Transitions |
|---|---|---|
| Awaiting email entry | User on 'forgot password' form |
|
| Reset email sent | User waiting for email link |
|
| Validating token | Server checking reset link validity |
|
| Setting new password | User entering new password with strength check |
|
| Complete | Password updated, all sessions invalidated | terminal |
Easy-to-miss situations
The kinds of edge cases that break demos.
What if attacker submits common emails to enumerate accounts?
highDifferent responses for "exists" vs "doesn't exist" leak account presence.
Suggested handling: Always return same message regardless of email existence. Don't include "we couldn't find that email" anywhere. Add rate limiting per IP.
What if user resets password while logged in on other devices?
highOld sessions still valid โ attacker holding old session keeps access.
Suggested handling: After password change, INVALIDATE all sessions for this user (force re-login everywhere). Show "Signed out from N devices" confirmation.
What if reset link is clicked from a previewer (Slack, email scanner)?
mediumLink gets "consumed" (marked used) before user even sees it.
Suggested handling: Don't mark token used on first GET. Only mark used when user submits new password. Use POST for state change, not GET.
What if user uses an extremely common password (123456)?
mediumAccount immediately vulnerable, defeats purpose of reset.
Suggested handling: Reject top 1000 most common passwords (use HaveIBeenPwned API or local list). Show "This password is too common โ try a unique phrase."
What if the email gets delayed by hours?
mediumToken expires before user receives it.
Suggested handling: Use 1-hour expiry (forgiving). Track email delivery time and alert ops if > 5 min average. On expired clicks, instantly offer "Send a new one" with one click.
Composes well with
Combine these patterns when you need a richer flow.