Designing a Secure Password Reset Flow with Django, Next.js, and dj-rest-auth
While building the authentication module for my Placement Portal, I realized that logging users in is only one part of account security.
Eventually, every application needs a way for users to regain access to their accounts.
That is where the password reset workflow becomes important.
At first glance, it seems like a simple feature. Enter your email, receive a link, create a new password, and you're done.
Behind the scenes, however, several security and architectural decisions make this process safe and reliable.
Step 1: Requesting a Password Reset
The process begins when a user enters their email address.
The Django backend receives this request and triggers the password reset endpoint provided by dj-rest-auth.
One security behavior immediately caught my attention.
Whether the email address exists or not, the API always returns HTTP 200 OK.
This prevents Email Enumeration Attacks, where attackers attempt to discover registered accounts by comparing server responses.
Every request receives the same response, making it impossible to determine whether an email belongs to a valid user.
Step 2: Sending the Reset Email
During development, Django's console email backend simply prints the email contents in the terminal.
For production, however, a real email provider is required.
I configured an SMTP email backend using Gmail App Passwords for development and testing.
Instead of sending plain text, the email uses an HTML template so important information can be highlighted and branded consistently.
This email contains a secure password reset link.
Step 3: Understanding the Reset Link
Unlike OTP-based password reset systems, dj-rest-auth generates a unique reset URL.
That URL contains two important values:
- UID
- Token
Together, these uniquely identify the password reset request.
Users never need to understand these values—they simply click the link provided in the email.
Step 4: Frontend Route Handling
Since my frontend is built with Next.js, I created a dynamic route that captures both the UID and Token directly from the URL.
When a user opens the reset link:
- The route extracts the UID.
- It extracts the Token.
- The reset password page is displayed.
The user simply enters:
- New Password
- Confirm Password
Everything else happens behind the scenes.
Step 5: Completing the Password Reset
When the form is submitted, the frontend sends:
- UID
- Token
- New Password
- Confirm Password
to the Django backend.
The backend validates:
- The reset token
- The UID
- Password confirmation
- Token validity
If everything is valid, the user's password is updated successfully.
Three Components Working Together
Implementing password reset taught me that this feature depends on three independent systems working together.
Frontend
Receives the password reset link, extracts the UID and Token, and collects the user's new password.
Backend
Generates secure reset tokens, validates requests, and updates user credentials.
Email Service
Delivers password reset emails using reusable HTML templates and SMTP integration.
Without any one of these components, the password reset workflow cannot function correctly.
Why Email Links Instead of OTP?
Many applications still rely on OTPs.
I chose an email link because it reduces friction for users.
Instead of copying and typing a six-digit code, users simply click the link in their inbox and continue directly to the password reset page.
It requires less effort while maintaining strong security through time-limited tokens.
Final Thoughts
Building a password reset feature taught me that security is rarely about a single API.
It's about designing a workflow where the frontend, backend, email infrastructure, and security best practices work together seamlessly.
Although users only see a single "Forgot Password" button, there's a carefully designed system operating behind it.
As I continue building this Placement Portal in public, I'm learning that the smallest features often involve the most thoughtful engineering decisions.