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.
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:
- 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.
- 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.
- 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.