Why It’s Important
Password reset is the highest-risk feature of your authentication stack. It is literally a way to bypass login — and if you get it wrong, you could hand over accounts to attackers.
Common mistakes include:
✅ reusing predictable reset tokens
✅ forgetting token expiration
✅ leaking reset tokens in URLs
✅ failing to invalidate used tokens
✅ not rotating session secrets
We’ll build a secure, production-grade password reset that avoids these traps.
1. High-Level Password Reset Flow
✅ User initiates reset (supply their email)
✅ Backend issues a cryptographically random token stored hashed in the DB
✅ Send a one-time link with the token to the user’s email
✅ User clicks link → new password form
✅ Backend verifies token (one-time, with expiry)
✅ Reset password + kill all other sessions
2. Sample Django Implementation (concept applies to any backend)
Let’s show real code with Django:
When user requests a reset:
✅ Note: store only a hashed version of the token
✅ Never reveal whether the email is registered
✅ Token expires in 1 hour
When user clicks the link:
✅ Validate token securely with constant-time comparison
✅ Expire the token after use
✅ Flush all sessions after a password change
3. Security Best Practices Checklist
✅ Hash tokens in DB (make_password
)
✅ Generate long random tokens (>=64 chars, cryptographically secure)
✅ Expire tokens (1 hour or less is best)
✅ Never reveal user existence
✅ Kill all active sessions after reset
✅ Use HTTPS only
✅ Rate-limit reset requests
✅ Log unusual resets for fraud detection
Common Mistakes
🚫 Using simple UUIDs as reset tokens (easily guessable with enough tries)
🚫 Leaving tokens in query strings for days (they can be leaked via browser history or referer headers)
🚫 Storing tokens in plaintext in your DB
🚫 Allowing unlimited resets without rate limiting
🚫 Failing to rotate user sessions after reset
Bonus: Links Without Query Strings
Many engineers get nervous about tokens in query strings. Modern best practice is to move them to the fragment (#
) or to a one-time code entry page. But the most common is a standard reset/<token>
pattern because:
It’s link-click friendly
Token is ephemeral
HTTPS protects it
If you worry about logs leaking query params, then avoid query strings, and embed token in the path.
Test Your Flow
✅ Confirm token cannot be reused
✅ Confirm expired token rejection
✅ Confirm correct user password updates
✅ Confirm other sessions die after reset
✅ Confirm no user enumeration is possible
Automated tests should cover these scenarios with tools like Django’s TestCase
.
Conclusion
A password reset is a door directly into an account — treat it as a high-value target.
By hashing your tokens, using cryptographic randomness, time-based expiration, and session rotation, you protect users while keeping your system trustworthy.
NEVER MISS A THING!
Subscribe and get freshly baked articles. Join the community!
Join the newsletter to receive the latest updates in your inbox.