Reset and Change/Forgot Password in Django Rest Framework

In this blog, we’ll cover the implementation of secure and user-friendly password management functionalities in a Django Rest Framework (DRF) project. Password security is critical for any web application, and providing users with the ability to change their passwords while logged in and reset them when forgotten is essential for a positive user experience. We’ll explore the steps to enable password change and reset functionalities, ensuring that your application’s users have a seamless and secure experience. Let’s get started!

For this tutorial, we are extending our previous blog on User Registration, Login, Logout API using DRF

Note: This step 1 and step 2 has been done in our previous blog, if you have already done this, then please skip this step.

Step 1: Setting Up Django Rest Framework

Make sure you have Django and DRF installed. If not, use the following pip command to install them:

pip install django djangorestframework

Create a new Django project and an app:

django-admin startproject mydrfproject
cd mydrfproject
python manage.py startapp accounts

Step 2: Creating the Custom User Model

In “accounts/models.py”, define the custom user model:

# accounts/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)

    # Add custom fields here, if needed

    def __str__(self):
        return self.username

Update the AUTH_USER_MODEL and REST_FRAMEWORK default authentication classes in “mydrfproject/settings.py”:

AUTH_USER_MODEL = 'accounts.CustomUser'

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    # Other settings...
}

Run migrations to create the custom user model table:

python manage.py makemigrations
python manage.py migrate

Step 3: Change/Update Password Implementation in DRF

Step 3.1: Serializers for Password Management

Create serializers.py within the “accounts” app directory to handle password change and reset data:

# accounts/serializers.py

from rest_framework import serializers

class ChangePasswordSerializer(serializers.Serializer):
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

class ResetPasswordEmailSerializer(serializers.Serializer):
    email = serializers.EmailField(required=True)

Step 3.2: Password Change Endpoint

In “accounts/views.py”, create a new API view for users to change their passwords when logged in:

# accounts/views.py

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import update_session_auth_hash
from .serializers import ChangePasswordSerializer

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def change_password(request):
    if request.method == 'POST':
        serializer = ChangePasswordSerializer(data=request.data)
        if serializer.is_valid():
            user = request.user
            if user.check_password(serializer.data.get('old_password')):
                user.set_password(serializer.data.get('new_password'))
                user.save()
                update_session_auth_hash(request, user)  # To update session after password change
                return Response({'message': 'Password changed successfully.'}, status=status.HTTP_200_OK)
            return Response({'error': 'Incorrect old password.'}, status=status.HTTP_400_BAD_REQUEST)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Step 3.3: Password Change URL

In “accounts/urls.py”, connect the new view to a URL:

# accounts/urls.py

from django.urls import path
from .views import change_password

urlpatterns = [
    # ...
    path('change_password/', change_password, name='change_password'),
    # ...
]

Step 4: Reset/Forgot Password Implementation in DRF

To enable password reset functionality, we’ll use Django’s built-in password reset framework.

Step 4.1: Install django-rest-passwordreset package and register the app.

Install the django-rest-passwordreset package. Open your terminal or command prompt and run the following command:

pip install django-rest-passwordreset

After installing the package, make sure to add it to the INSTALLED_APPS in your “mydrfproject/settings.py” file:

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework.authtoken',
    'accounts',
    'django_rest_passwordreset',  # Add this line
    # ...
]

Step 4.2: URL Configuration

In the project’s main “mydrfproject/urls.py”, include the password reset URLs:

# mydrfproject/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('accounts.urls')),  # Include the app's URLs
    path('api/password_reset/', include('django_rest_passwordreset.urls', namespace='password_reset')),
]

Step 4.3: Email Configuration for Change Reset Password

Ensuure you have email settings configured in “mydrfproject/settings.py” to send password reset emails to users. Specify the email backend and other relevant settings, such as EMAIL_HOST, EMAIL_PORT, EMAIL_USE_TLS, EMAIL_HOST_USER, and EMAIL_HOST_PASSWORD.

Step 4.3.1: Setting Up the Email Backend

To set up the email backend, open “mydrfproject/settings.py” and add the following configurations:

# mydrfproject/settings.py

# Email Backend Configuration
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'  # Replace with your preferred backend

EMAIL_PORT = 587  # Replace with your email port
EMAIL_USE_TLS = True  # Set to False if your email server doesn't use TLS
EMAIL_HOST = 'your_email_host'  # Replace with your email host for gmail -> 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_email_username'  # Replace with your email username
EMAIL_HOST_PASSWORD = 'your_email_password'  # Replace with your email password

Please watch bellow video, to send otp using Gmail.

Gmail SMTP Email settings for Django Project

As we will be keeping our email templates in our root directory, please create a folder with name “templates” in root project directory and add a chnage in TEMPLATES > settings.py file.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR, 'templates/',],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Step 4.3.2: Adding Reset Password Signals to Send Email

Create signals.py within the “accounts” app directory to handle reset password email:

# accounts/signals.py

from django.core.mail import EmailMultiAlternatives
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.urls import reverse

from django_rest_passwordreset.signals import reset_password_token_created


@receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args, **kwargs):
    """
    Handles password reset tokens
    When a token is created, an e-mail needs to be sent to the user
    :param sender: View Class that sent the signal
    :param instance: View Instance that sent the signal
    :param reset_password_token: Token Model Object
    :param args:
    :param kwargs:
    :return:
    """
    # send an e-mail to the user
    context = {
        'current_user': reset_password_token.user,
        'username': reset_password_token.user.username,
        'email': reset_password_token.user.email,
        'reset_password_url': "{}?token={}".format(
            instance.request.build_absolute_uri(reverse('password_reset:reset-password-confirm')),
            reset_password_token.key)
    }

    # render email text
    email_html_message = render_to_string('email/password_reset_email.html', context)
    email_plaintext_message = render_to_string('email/password_reset_email.txt', context)

    msg = EmailMultiAlternatives(
        # title:
        "Password Reset for {title}".format(title="Your Website Title"),
        # message:
        email_plaintext_message,
        # from:
        "[email protected]",
        # to:
        [reset_password_token.user.email]
    )
    msg.attach_alternative(email_html_message, "text/html")
    msg.send()

Import the Signals in “accounts/apps.py”: Open the “apps.py” file in the same app’s directory and add an import statement for the “signals.py” file:

# accounts/apps.py

from django.apps import AppConfig

class AccountsConfig(AppConfig):
    name = 'accounts'

    def ready(self):
        import accounts.signals  # Add this line to import the signals.py

Update the “apps” Setting in “settings.py”: Update the “INSTALLED_APPS” setting in your project’s “settings.py” file to include your app with the updated AppConfig:

# mydrfproject/settings.py

INSTALLED_APPS = [
    # ...
    'accounts.apps.AccountsConfig',  # Update the app's reference
    # ...
]

Step 4.3.3: Customizing Email Templates

To customize the password reset email templates, create a new directory named “email” inside the “templates” directory you just specified. Place the customized email templates inside it. The templates you need to customize are:

  • password_reset_email.html: The HTML email template for the password reset email.
  • password_reset_email.txt: The plain text version of the password reset email.

Here’s an example of a custom “password_reset_email.html” template:

<!-- mydrfproject/templates/email/password_reset_email.html -->

<!DOCTYPE html>
<html>
<head>
    <title>Password Reset Email</title>
</head>
<body>
    <p>Hello,</p>
    <p>We've received a request to reset your password. Please click on the link below to reset your password:</p>
    <p><a href="{{ password_reset_url }}">Reset Password</a></p>
    <p>If you did not request this password reset, you can safely ignore this email.</p>
    <p>Best regards,</p>
    <p>Your Application Team</p>
</body>
</html>

Here’s an example of a custom “password_reset_email.txt” template:

Hello {{username}}, We've received a request to reset your password. Please click on the link below to reset your password: {{ reset_password_url }}

Ensure you customize the email content and design to match your application’s needs.

Step 5: Testing the Password Management

With the password change and reset functionalities implemented, you can now test them using cURL or a REST API client like Postman.

a. Password Change:

curl -X POST -H "Authorization: Token YOUR_AUTH_TOKEN" -H "Content-Type: application/json" -d '{"old_password": "your_old_password", "new_password": "your_new_password"}' http://localhost:8000/api/change_password/

b. Password Reset: (Replace email with your registration email)

curl -X POST -H "Content-Type: application/json" -d '{"email": "[email protected]"}' http://localhost:8000/api/password_reset/

Once you hit the password_reset url, you will get email in your mail box, if not received then check spam. In the mail you find url similar to this – http://localhost:8000/api/password_reset/confirm/?token=60d9dd6559500cc469. In that url you will find token parameter, copy so that we will use it in our cURL, like below:

curl -X POST -H "Content-Type: application/json" -d '{"password":"Password@123", "token": "60d9dd6559500cc469"}' http://localhost:8000/api/password_reset/confirm/

Conclusion

Congratulations! You’ve successfully implemented secure and user-friendly password management functionalities in your Django Rest Framework project. Users can now change their passwords while logged in and reset their passwords when forgotten. Password security is crucial, and by following this guide, you’ve provided a robust and seamless experience for your application’s users. Remember to thoroughly test your implementation and follow best practices to ensure a secure password management system. If you encounter any issues, feel free to seek assistance.

Find this tutorial on Github.

Blogs You Might Like to Read!