Login with OTP via Email/Phone in Django Rest Framework

In this blog, we will walk through the process of enhancing the login functionality in Django Rest Framework (DRF) by implementing One-Time Password (OTP) verification via email or phone number. By adding OTP authentication, we’ll security of our application and ensure that only authorized users can access their accounts. We’ll cover all necessary changes to the models, views, and URL configurations to achieve this feature. 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 9 has been done in our previous blog, if you have already done this, then please skip this steps.

Step 1: Setting Up Django Rest Framework

First, ensure 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 app:

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

Step 2: Configuring Email Settings

To enable email functionality, configure the email settings in “mydrfproject/settings.py”:

# mydrfproject/settings.py

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

EMAIL_HOST = 'your_email_host'  # Replace with your email host
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_USER = 'your_email_username'  # Replace with your email username
EMAIL_HOST_PASSWORD = 'your_email_password'  # Replace with your email password

Replace the placeholders (your_email_host, your_email_username, and your_email_password) with your actual email server details.

Please watch bellow video, to send otp using Gmail.

Watch this Video to Create Gmail SMTP Email settings for Django Project

Step 3: Create a Custom User Model

In “accounts/models.py,” create a custom user model with an additional “otp” field:

# accounts/models.py

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

class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)
    otp = models.CharField(max_length=6, null=True, blank=True)  # Add the otp field here

    # Add custom fields here, if needed

    def __str__(self):
        return self.username

Update the AUTH_USER_MODEL in “mydrfproject/settings.py”:

AUTH_USER_MODEL = 'accounts.CustomUser'

Run migrations to create the custom user model table:

python manage.py makemigrations
python manage.py migrate

Step 4: Generate and Send OTP via Email

Create a utility function in “accounts/utils.py” to generate a random OTP and send it via email:

# accounts/utils.py

import random
import string
from django.core.mail import send_mail
from django.conf import settings

def generate_otp(length=6):
    characters = string.digits
    otp = ''.join(random.choice(characters) for _ in range(length))
    return otp

def send_otp_email(email, otp):
    subject = 'Your OTP for Login'
    message = f'Your OTP is: {otp}'
    from_email = settings.EMAIL_HOST_USER
    recipient_list = [email]
    send_mail(subject, message, from_email, recipient_list)

Step 5: Generate and Send OTP Phone

To send the OTP via phone instead of email, you’ll need to make changes in the send_otp_phone function within accounts/utils.py. Instead of using the send_mail function to send an email, you’ll use a third-party SMS API or service to send the OTP as a text message to the user’s phone number.

Here’s a general outline of the changes you need to make:

Step 5.1: Choose an SMS API/Service Provider

First, you’ll need to choose an SMS API or service provider that allows you to send SMS messages programmatically. There are various options available, such as Twilio, Nexmo, Plivo, etc. Sign up for an account with your chosen provider and obtain the necessary credentials (e.g., API key, authentication token).

Step 5.2: Install Required Packages

Depending on the SMS service provider you choose, you might need to install their Python package or use a third-party package that integrates with the SMS service. For example, if you choose Twilio, you’ll need to install the twilio package:

pip install twilio

Step 5.3: Update send_otp_phone Function

Now, modify the send_otp_phone function in accounts/utils.py to use the SMS API to send the OTP as a text message:

# accounts/utils.py

import random
import string
from django.conf import settings
from twilio.rest import Client  # Import the Twilio Client if using Twilio

def generate_otp(length=6):
    characters = string.digits
    otp = ''.join(random.choice(characters) for _ in range(length))
    return otp

def send_otp_phone(phone_number, otp):
    account_sid = 'your_account_sid'  # Replace with your Twilio account SID
    auth_token = 'your_auth_token'  # Replace with your Twilio auth token
    twilio_phone_number = 'your_twilio_phone_number'  # Replace with your Twilio phone number

    client = Client(account_sid, auth_token)
    message = client.messages.create(
        body=f'Your OTP is: {otp}',
        from_=twilio_phone_number,
        to=phone_number
    )

Replace the placeholders (your_account_sid, your_auth_token, and your_twilio_phone_number) with your actual Twilio credentials and Twilio phone number.

Step 6: Implement Login with OTP View

In “accounts/views.py,” create a new API view to handle login with OTP:

# accounts/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .utils import generate_otp, send_otp_email
from .models import CustomUser

class LoginWithOTP(APIView):
    def post(self, request):
        email = request.data.get('email', '')
        try:
            user = CustomUser.objects.get(email=email)
        except CustomUser.DoesNotExist:
            return Response({'error': 'User with this email does not exist.'}, status=status.HTTP_404_NOT_FOUND)

        otp = generate_otp()
        user.otp = otp
        user.save()

        send_otp_email(email, otp)
        # send_otp_phone(phone_number, otp)

        return Response({'message': 'OTP has been sent to your email.'}, status=status.HTTP_200_OK)

Step 7: Validate OTP and Authenticate User

Update the “ValidateOTP” view in “accounts/views.py” to validate the OTP and authenticate the user:

# accounts/views.py

from django.contrib.auth import authenticate
from rest_framework.authtoken.models import Token

class ValidateOTP(APIView):
    def post(self, request):
        email = request.data.get('email', '')
        otp = request.data.get('otp', '')

        try:
            user = CustomUser.objects.get(email=email)
        except CustomUser.DoesNotExist:
            return Response({'error': 'User with this email does not exist.'}, status=status.HTTP_404_NOT_FOUND)

        if user.otp == otp:
            user.otp = None  # Reset the OTP field after successful validation
            user.save()

            # Authenticate the user and create or get an authentication token
            token, _ = Token.objects.get_or_create(user=user)

            return Response({'token': token.key}, status=status.HTTP_200_OK)
        else:
            return Response({'error': 'Invalid OTP.'}, status=status.HTTP_400_BAD_REQUEST)

Step 8: URL Configuration

Define the URLs for login with OTP and OTP validation in “accounts/urls.py”:

# accounts/urls.py

from django.urls import path
from .views import LoginWithOTP, ValidateOTP

urlpatterns = [
    # ...
    path('login-with-otp/', LoginWithOTP.as_view(), name='login-with-otp'),
    path('validate-otp/', ValidateOTP.as_view(), name='validate-otp'),
]

Step 9: Include App URLs

Include the app’s URLs in the main project’s “mydrfproject/urls.py”:

# 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')),
]

Step 10: Testing the OTP Functionality

Here are the cURL commands to test the “Login with OTP using email” API endpoints:

  1. Request OTP for Login:
curl -X POST -H "Content-Type: application/json" -d '{"email": "[email protected]"}' http://localhost:8000/api/login-with-otp/

Replace “[email protected]” with the email address for which you want to request the OTP.

  1. Validate OTP and Authenticate User:
curl -X POST -H "Content-Type: application/json" -d '{"email": "[email protected]", "otp": "123456"}' http://localhost:8000/api/validate-otp/

Replace “[email protected]” with the email address and “123456” with the OTP received on that email.

  1. Request Phone OTP for Login:

To test the OTP sending functionality via phone using cURL, you can simulate the API request with the following command:

curl -X POST -H "Content-Type: application/json" -d '{"phone_number": "+1234567890"}' http://localhost:8000/api/login-with-otp/

With these cURL commands, you can test the login with OTP functionality and ensure that the API is working as expected.

Conclusion

Congratulations! You have successfully implemented a secure login with OTP via email in Django Rest Framework (DRF). By integrating email functionality, generating and sending OTPs, and validating them during login, you have significently enhanced the security of your application. Users will now receive OTPs on their registered email addresses and can use them to securely log in to your application. OTP-based login is an excellent way to safeguard user accounts from unauthorized access. You can further customize the implementation, add error handling, and enhance the user experience to meet your specific project requirements. Happy coding!

Find this tutorial on Github.

Blogs You Might Like to Read!