Secure Password Change and Reset with Django and Next.js: Complete Guide

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

EndpointMethodDescription
/change-password/PUTChange password (auth required)
/reset-password/POSTSend reset email (by email)
/reset-password-confirm/POSTReset 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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top