Overview
Managing user authentication goes beyond login and registration — users also need to change their password securely and recover it via email when forgotten.
In this guide, you’ll learn how to:
- Let authenticated users change their password using their current password
- Enable users to reset password via email (secure token)
- Integrate Django Rest Framework + Next.js
- Use JWT or session-based auth with form handling
Project Structure
/backend (Django)
└── accounts/
├── urls.py
├── views.py
├── serializers.py
/frontend (Next.js)
└── app/account/change-password
└── app/auth/forgot-password
└── app/auth/reset-password
Part 1: Change Password (With Current Password)
✅ Backend (Django DRF)
Serializer:
# accounts/serializers.py
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
class ChangePasswordSerializer(serializers.Serializer):
current_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True, validators=[validate_password])
def validate(self, attrs):
user = self.context['request'].user
if not user.check_password(attrs['current_password']):
raise serializers.ValidationError({"current_password": "Incorrect password."})
return attrs
def save(self, **kwargs):
user = self.context['request'].user
user.set_password(self.validated_data['new_password'])
user.save()
return user
View:
# accounts/views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import ChangePasswordSerializer
class ChangePasswordView(APIView):
permission_classes = [IsAuthenticated]
def put(self, request):
serializer = ChangePasswordSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response({"detail": "Password changed successfully."})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
URL:
# accounts/urls.py
from django.urls import path
from .views import ChangePasswordView
urlpatterns = [
path("change-password/", ChangePasswordView.as_view()),
]
✅ Frontend (Next.js)
// app/account/change-password/page.tsx
"use client";
import { useState } from "react";
import axios from "@/lib/api";
export default function ChangePassword() {
const [form, setForm] = useState({
current_password: "",
new_password: "",
});
const [message, setMessage] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await axios.put("/change-password/", form);
setMessage("✅ Password changed successfully");
} catch (err) {
setMessage("❌ Failed to change password");
}
};
return (
<form onSubmit={handleSubmit}>
<input name="current_password" type="password" placeholder="Current Password" onChange={handleChange} />
<input name="new_password" type="password" placeholder="New Password" onChange={handleChange} />
<button type="submit">Change Password</button>
{message && <p>{message}</p>}
</form>
);
}
🧠 Part 2: Forgot Password via Email (Password Reset Flow)
✅ Backend (Django)
We’ll use Django’s built-in password reset workflow.
a. Email Configuration
In settings.py
:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# Use SMTP backend in production
b. Password Reset Views
Use Django’s built-in views OR your own DRF views.
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.models import User
from django.core.mail import send_mail
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_str
class PasswordResetRequestView(APIView):
def post(self, request):
email = request.data.get("email")
user = get_object_or_404(User, email=email)
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
reset_link = f"{settings.FRONTEND_URL}/auth/reset-password?uid={uid}&token={token}"
send_mail(
subject="Reset your password",
message=f"Click the link to reset your password: {reset_link}",
from_email="noreply@example.com",
recipient_list=[email],
)
return Response({"detail": "Reset link sent to your email."})
class PasswordResetConfirmView(APIView):
def post(self, request):
uid = force_str(urlsafe_base64_decode(request.data.get("uid")))
token = request.data.get("token")
new_password = request.data.get("new_password")
user = get_object_or_404(User, pk=uid)
if not default_token_generator.check_token(user, token):
return Response({"error": "Invalid token"}, status=400)
user.set_password(new_password)
user.save()
return Response({"detail": "Password reset successful."})
URLs
urlpatterns += [
path("reset-password/", PasswordResetRequestView.as_view()),
path("reset-password-confirm/", PasswordResetConfirmView.as_view()),
]
✅ Frontend (Next.js)
a. Forgot Password Page
// app/auth/forgot-password/page.tsx
"use client";
import { useState } from "react";
import axios from "@/lib/api";
export default function ForgotPassword() {
const [email, setEmail] = useState("");
const [status, setStatus] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await axios.post("/reset-password/", { email });
setStatus("📧 Reset link sent to your email.");
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email" />
<button type="submit">Send Reset Link</button>
{status && <p>{status}</p>}
</form>
);
}
b. Reset Password Page
// app/auth/reset-password/page.tsx
"use client";
import { useSearchParams } from "next/navigation";
import { useState } from "react";
import axios from "@/lib/api";
export default function ResetPassword() {
const params = useSearchParams();
const uid = params.get("uid") || "";
const token = params.get("token") || "";
const [password, setPassword] = useState("");
const [status, setStatus] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await axios.post("/reset-password-confirm/", { uid, token, new_password: password });
setStatus("✅ Password has been reset.");
};
return (
<form onSubmit={handleSubmit}>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="New Password" />
<button type="submit">Reset Password</button>
{status && <p>{status}</p>}
</form>
);
}
API Summary
Endpoint | Method | Description |
---|---|---|
/change-password/ | PUT | Change password (auth required) |
/reset-password/ | POST | Send reset email (by email) |
/reset-password-confirm/ | POST | Reset password using token & uid |
Final Outcome
You now have:
- A secure password change page with current password verification
- A forgot password flow with email link and token
- Full frontend + backend integration using Django + Next.js