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.
Table of Content
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.
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:
"noreply@yourdomain.com",
# 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": "your_email@example.com"}' 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.