Problem: Typical Mistakes in Implementing Password Recovery
The standard Forgot Password flow seems simple: the user enters an email, receives a link, sets a new password. But in practice, developers make mistakes that lead to vulnerabilities: revealing registration status, token guessing, rate limit bypass. We configure the flow on Laravel using the built-in Password Broker: the token is generated, hashed with bcrypt, and stored in the password_resets table with a TTL of 60 minutes. The response to a recovery request is identical for both existing and non-existing emails — an attacker cannot tell if a user is registered. OWASP recommends bcrypt for token hashing, and we follow this practice.
How the Recovery Flow Works?
The user sends a POST request to /forgot-password with their email. The server creates a token, hashes it, and stores it in the DB. Then an email is sent with a link like https://example.com/reset-password?token=...&email=.... The user clicks the link, sees a form for a new password. After submitting POST /reset-password, the server verifies the token (compares the bcrypt hash), updates the password, and removes the token. All active user sessions are invalidated.
What Vulnerabilities Does the Standard Implementation Hide?
Storing the Token in Plaintext
If the token is stored in plaintext, a database breach allows an attacker to immediately reset any user's password. We use a bcrypt hash — even a compromised database does not reveal tokens. bcrypt is specifically designed for password hashing: it is slow and includes a salt. MD5 and SHA-1/2 allow millions of combinations per second, while bcrypt slows brute force by a factor of 1000, making an attack impractical.
Lack of Rate Limiting
Without rate limiting, an attacker can flood the server with reset requests, causing load on the mail server. We set a limit: no more than 3 requests per hour per email or IP.
Identical Response for Existing and Non-Existing Emails
Many return different errors ("email not found" vs "email sent"), allowing an attacker to enumerate the email base. Our solution always responds identically: "If the email is registered, the email has been sent."
Why bcrypt Instead of MD5/SHA?
bcrypt is specifically designed for password hashing: it is slow and includes a salt. MD5 and SHA-1/2 are fast hashes that allow an attacker to try millions of combinations per second. Bcrypt slows brute force by a factor of 1000 or more, making an attack impractical. This means our bcrypt approach is 1000 times safer than storing the token in plaintext.
How We Implement Password Recovery Turnkey
We use the Laravel Password Broker, which provides ready-made methods for generating, verifying, and deleting tokens. Example controller:
use Illuminate\Support\Facades\Password; class ForgotPasswordController extends Controller { public function __invoke(Request $request) { $request->validate(['email' => 'required|email']); $status = Password::sendResetLink($request->only('email')); return $status === Password::RESET_LINK_SENT ? back()->with(['status' => __($status)]) : back()->withErrors(['email' => __($status)]); } } For password reset we use Password::reset().
Token storage: password_resets table with fields email, token (bcrypt), created_at. The token lives 60 minutes, then is removed via cron or garbage collection. Example table:
| token (bcrypt) | created_at | |
|---|---|---|
| [email protected] | $2y$10$... | — |
| [email protected] | $2y$10$... | — |
Thanks to bcrypt, even if the DB is leaked, tokens cannot be decrypted — this is 1000 times safer than plaintext.
Typical Implementation Mistakes
- Storing the token in plaintext
- Lack of rate limiting
- Different responses for existing/non-existing emails
- No session invalidation after password change
- Too long token TTL (more than 1 hour)
Comparison of Approaches for Reliability
| Approach | Security | Complexity |
|---|---|---|
| bcrypt-hashed token (ours) | High | Low |
| Plaintext token | Low | Very low |
| JWT token with short TTL | Medium | Medium |
Our approach is the optimal balance of security and maintainability. JWT with short TTL requires additional infrastructure and does not protect against token leakage if the secret key is compromised. Certified engineers guarantee implementation quality.
Work Process
- Analysis of the current authentication system and risk identification.
- Flow design: endpoints, validation, rate limiting.
- Implementation of controllers, migrations, custom email template.
- Testing: unit tests for reset and recovery, integration tests for rate limiting.
- Deployment and monitoring.
Timeline and What's Included
Typically implementation takes 1–2 business days. Includes:
- Custom controller and routes
- Migration for the password_resets table
- Custom email template (HTML+text)
- Rate limiting (3 requests/hour per email)
- Session invalidation upon password change
- Tests and documentation
We have many years of experience and have completed over 30 authentication projects. Get a consultation — we will assess your project for free. Order a turnkey secure password recovery implementation — contact us.







