TL;DR
Single-use password reset links are convenient but have security weaknesses (phishing, link expiry issues). Better options include magic links sent to a registered email address, one-time passcodes (OTP) via SMS or authenticator apps, and WebAuthn/Passkeys. This guide explains how to implement these alternatives.
1. Understand the Risks of Password Reset Links
Password reset links are often targeted by attackers because:
- Phishing: Attackers can create fake emails that look legitimate, tricking users into clicking malicious links.
- Link Expiry: Short expiry times improve security but cause user frustration if they don’t reset immediately. Longer expiry times increase risk.
- Replay Attacks: If a link is intercepted, it can be used even after the user intended to discard it.
2. Magic Links (Email-Based)
Magic links send a temporary login link directly to the user’s registered email address. This avoids passwords altogether for reset.
- Generate a Unique Token: When a user requests a reset, create a cryptographically secure random token (e.g., using UUID).
- Store Token & User Association: Store the token linked to the user’s account in your database with an expiry timestamp (e.g., 15 minutes).
- Send Email with Link: Send an email containing a link like
https://yourdomain.com/reset?token=[unique_token]. - Verify Token on Reset Request: When the user clicks the link, verify the token exists, hasn’t expired, and matches the requesting user.
- Allow Password Change: If valid, allow the user to set a new password. Immediately invalidate the token after use.
Example (Python with Flask):
from uuid import uuid4
import datetime
# ... database code omitted for brevity...
def generate_reset_token():
return str(uuid4())
def send_magic_link(email, token):
# Send email with link: https://yourdomain.com/reset?token=token
pass # Replace with your email sending logic
3. One-Time Passcodes (OTP)
OTP sends a temporary code to the user via SMS or an authenticator app.
- Generate OTP: Create a 6-8 digit random number.
- Send OTP: Send the OTP to the user’s registered phone number (SMS) or through their authenticator app.
- Verify OTP on Reset Request: When the user enters the code, verify it matches the one you sent and hasn’t expired (e.g., 5 minutes).
- Allow Password Change: If valid, allow password reset.
Important Considerations for SMS OTP:
- SMS is less secure than authenticator apps due to SIM swapping attacks.
- Use a reliable SMS gateway provider.
4. WebAuthn/Passkeys
WebAuthn (and its related Passkey standard) allows users to use biometric authentication or hardware security keys for passwordless login and reset.
- User Registration: The user registers a WebAuthn credential (e.g., fingerprint, face ID, security key).
- Reset Request: When requesting a reset, the system prompts the user to authenticate using their registered WebAuthn credential.
- Authentication & Password Change: If authentication succeeds, allow password reset without requiring a traditional password or OTP.
Benefits of WebAuthn/Passkeys:
- Highly secure – resistant to phishing and many other attacks.
- User-friendly experience.
Resources: Explore libraries like webauthn in Python or the Mozilla WebAuthn documentation.
5. Security Best Practices (For All Methods)
- Rate Limiting: Limit the number of reset requests from a single IP address or user account to prevent brute-force attacks.
- Account Lockout: Temporarily lock accounts after multiple failed reset attempts.
- Token/OTP Storage Security: Protect tokens and OTPs in your database using strong encryption.
- HTTPS Only: Always use HTTPS to encrypt communication between the user and your server.

