Password reset: the 7 mistakes that open up your accounts
The weak link in authentication
You've polished your login: bcrypt-hashed passwords, MFA available, rate limiting in place. But the "forgot password" flow bypasses all of it by design: it allows taking over an account without knowing the password. That's why attackers test it first — and why we do too.
Here are the 7 mistakes we encounter most often, from most severe to most subtle.
1. Predictable or short token
A reset token built from a timestamp, a base64-encoded user ID, or a UUID v1 (which embeds the timestamp and MAC address) can be guessed or brute-forced. You need at least 128 bits of entropy from a cryptographic generator (crypto.randomBytes, not Math.random()).
2. Token without expiration
A reset link sent 6 months ago that still works is one more live link sitting in a compromised mailbox. Recommended maximum lifetime: 15 to 60 minutes.
3. Token not invalidated after use
The token must be single-use. If it stays valid after the password change, an attacker who intercepted the email can re-reset the account afterwards. Same requirement: invalidate all previous tokens when a new one is requested.
4. Host header injection
The most elegant mistake. If your backend builds the reset link from the request's Host header, an attacker requests a reset for the victim while sending Host: attacker.com. The victim receives a legitimate email… with a link pointing to the attacker's domain, token included. The link must be built from a base URL defined in configuration, never from the request.
5. User enumeration
"No account associated with this address" vs "Email sent": this difference in messaging lets anyone build a list of your SaaS customers. Identical response in both cases — response time included.
6. Sessions not revoked after reset
The scenario: an attacker already has an active session on the account (leaked password, stolen session). The victim changes their password to kick them out… but the attacker's session stays valid. Any password change must revoke all existing sessions and refresh tokens.
7. No rate limiting on reset requests
Without limits, the feature becomes a spam cannon aimed at your users — and a mass-testing tool for mistakes 1 through 5.
Checklist
These 7 points are part of the systematic checks in our First Review: 48 hours, real-world conditions, no access to your code required.
Related articles
Three adjacent analyses to keep exploring the same attack surface.
Firebase Firestore: why 'allow read, write: if request.auth != null' is not security
The basic Firestore authentication rule doesn't protect your data. Here's why and how to fix it.
CVE-2026-32541: Next.js middleware bypass — analysis and fix
A critical vulnerability in Next.js middleware allowed authentication bypass. Technical analysis of the flaw and fix guide for HR SaaS vendors.
Laravel: when Ziggy exposes the complete map of your application
Ziggy route exposure gives attackers a complete map of your Laravel app architecture.
Sources
Related services
If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.