Login with OTP Authentication in Django and Django REST Framework
Secure your Django apps and Django REST framework APIs with easy OTP login via phone number. Add an extra layer of safety and simplicity.
Django and Django REST Framework (DRF)! These powerful frameworks help you build efficient and scalable web applications using Python. Today, I’m focusing on an increasingly popular authentication method — Login with OTP Authentication.
OTP (one-time password) authentication adds an extra layer of security to your application while keeping the user experience smooth. Instead of relying solely on passwords, you can give users a temporary, single-use password sent to their phone or email.
By the end of this guide, you’ll know how to integrate OTP authentication in your Django and DRF projects. You’ll enhance security, create a better user experience, and protect your app from potential threats.
I’ll take you through every step of the process, from setting up your project to generating and verifying OTPs. So, let’s get started on making your application safer and more user-friendly!
What is OTP Authentication?
OTP authentication, or one-time password authentication, is a security process that requires the user to provide a temporary password sent to their phone number or email address. This temporary password is valid for a short time and is used only once for login verification.
Types of OTP Delivery Methods:
SMS: Sending the OTP to the user’s phone number via SMS.
Email: Delivering the OTP to the user’s email address.
Authenticator Apps: Generating OTPs using an app like Google Authenticator.
Advantages of OTP Authentication
Here are some of the benefits of using OTP authentication in your Django and DRF projects:
Enhanced Security: OTPs provide an additional layer of security beyond traditional password methods. Even if a password is compromised, the OTP adds an extra step for verification.
User-Friendly Experience: OTP authentication is relatively easy for users, as they receive a direct message on their phone or email, and they don’t need to remember additional information.
Flexibility in Implementation: Depending on your requirements, you can choose various methods to send OTPs, such as SMS, email, or authenticator apps.
Implementing OTP Authentication in Django & DRF
Let’s take a look at how to implement OTP authentication in a Django project:
Installing Necessary Packages
You’ll need the following packages:
pip install djangorestframework
pip install django
pip install Pillow
pip install djangorestframework-simplejwt
pip install requests
Setting Up the Django Project
If you haven’t already, start by setting up a new Django project:
django-admin startproject myproject
cd myproject
Create an app:
Once you’re inside your project folder, create a new app where you can build your OTP authentication system. Replace my_app with your desired app name:
django-admin startapp my_app
Configuring the Project
Update your settings file (settings.py) to include Django REST Framework and the following necessary configurations:
INSTALLED_APPS = [
# Other apps
'rest_framework',
'Rest_framework_simplejwt'
'my_app' # newly created app
]
# Other settings…
With these steps, your Django project and app are ready for further development. Now, you can proceed with implementing OTP authentication.
Integrating Django REST Framework
Overview of Django REST Framework
Django REST Framework (DRF) is a flexible toolkit for building APIs in Django. It’s powerful and user-friendly, making it the go-to choice for many developers.
Creating an OTP Model
Design a model to store OTPs and associated user information in my_app directory ‘models.py’:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import RegexValidator
class User(AbstractUser):
phone = models.CharField(max_length=10,unique=True, blank=True, null=True, validators=[RegexValidator(
regex=r"^\d{10}", message="Phone number must be 10 digits only.")])
address = models.TextField(max_length=50, null=True, blank=True)
dob = models.DateField(null=True, blank=True)
otp = models.CharField(max_length=6, null=True, blank=True)
otp_expiry = models.DateTimeField(blank=True, null=True)
max_otp_try = models.CharField(max_length=2, default=3)
otp_max_out = models.DateTimeField(blank=True, null=True)
Once you’ve created the model in your app’s ‘models.py’ file, you must update your database schema to include the new model. Here’s how you do that:
- Make migrations: This step prepares the database for changes. Run the following command in your terminal to create migrations for the new model:
python manage.py makemigrations
- Apply the migrations: Now, apply the migrations to update the database with the new model. Run this command in your terminal:
python manage.py migrate
By running these commands, you ensure your database is set up to handle the new OTP model. Once you’ve set up the model and updated the database, you can start using the model to manage OTPs in your application!
Sending OTPs
You can use SMS for OTP delivery to your users’ mobile phones. Here’s how you can set up the process with the 2factor SMS service:
You can register here if you don’t have an account with 2factor SMS. Once you have your account set up, return to this article to continue the OTP setup in your application.
First, look at the ‘send_otp’ function in the ‘util.py’ file. This function handles the SMS delivery of your OTP:
import requests
from myproject import settings
def send_otp(mobile, otp):
"""
Send OTP via SMS.
"""
url = f"https://2factor.in/API/V1/{settings.SMS_API_KEY}/SMS/{mobile}/{otp}/Your OTP is"
payload = ""
headers = {'content-type': 'application/x-www-form-urlencoded'}
response = requests.get(url, data=payload, headers=headers)
print(response.content)
return bool(response.ok)
Now that you know how to send OTPs via SMS, you can use this function in your OTP authentication process to deliver OTPs to your users’ mobile devices. This setup helps ensure your users receive their OTPs quickly and reliably.
Creating a User Login & Register with OTP
Building the Login & Register View
Now, let’s focus on creating a user login with OTP. You need two views: LoginView and VerifyOTPView. Here’s how they work and how you can integrate them into your application.
LoginView
The LoginView handles OTP generation for the user when they attempt to log in. Here’s how it works:
Get the user’s phone number: The view starts by retrieving the user’s phone number from the requested data.
Find the user: It looks up the user in the database using the phone number. If the user doesn’t exist, it creates a new user record.
Check OTP attempts: The view checks if the user has reached the maximum allowed OTP tries. If they have, it returns an error message indicating that they should try again after an hour.
Generate an OTP: If the user hasn’t reached the maximum tries, it generates a random OTP and sets an expiration time for the OTP (10 minutes).
Update user record: The view updates the user’s record with the new OTP, its expiration time, and the remaining number of tries.
Send the OTP: It calls the send_otp function to deliver the OTP via SMS to the user’s phone.
Respond: Finally, it sends a response indicating that the OTP has been successfully generated.
Here’s an example of the code in ‘views.py’:
from .utils import send_otp
import datetime
from django.utils import timezone
from rest_framework.permissions import BasePermission,AllowAny, IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import viewsets, status
from django.contrib.auth import authenticate, login
from rest_framework_simplejwt.tokens import RefreshToken
import random
from django.core.exceptions import ObjectDoesNotExist
from .models import *
class LoginView(APIView):
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
phone = request.data.get('phone')
print(phone)
try:
user = User.objects.get(phone=phone)
print(user)
# Check for max OTP attempts
if int(user.max_otp_try) == 0 and user.otp_max_out and timezone.now() < user.otp_max_out:
return Response(
"Max OTP try reached, try after an hour",
status=status.HTTP_400_BAD_REQUEST,
)
# Generate OTP and update user record
otp = random.randint(1000, 9999)
otp_expiry = timezone.now() + datetime.timedelta(minutes=10)
max_otp_try = int(user.max_otp_try) - 1
user.otp = otp
user.otp_expiry = otp_expiry
user.max_otp_try = max_otp_try
if max_otp_try == 0:
otp_max_out = timezone.now() + datetime.timedelta(hours=1)
elif max_otp_try == -1:
user.max_otp_try = 3
else:
user.otp_max_out = None
user.max_otp_try = max_otp_try
user.save()
print(user.otp, 'OTP', user.phone)
send_otp(user.phone, otp, user)
return Response("Successfully generated OTP", status=status.HTTP_200_OK)
except ObjectDoesNotExist:
user_ = User.objects.create(phone=phone)
print(user_)
otp = random.randint(1000, 9999)
otp_expiry = timezone.now() + datetime.timedelta(minutes=10)
max_otp_try = int(user_.max_otp_try) - 1
user_.otp = otp
user_.otp_expiry = otp_expiry
user_.max_otp_try = max_otp_try
if max_otp_try == 0:
otp_max_out = timezone.now() + datetime.timedelta(hours=1)
elif max_otp_try == -1:
user_.max_otp_try = 3
else:
user_.otp_max_out = None
user_.max_otp_try = max_otp_try
user_.is_passenger = True
user_.save()
send_otp(user_.phone, otp, user_)
return Response("Successfully generated OTP", status=status.HTTP_200_OK)
else:
return Response("Phone number is incorrect", status=status.HTTP_401_UNAUTHORIZED)
VerifyOTPView
The VerifyOTPView handles OTP verification when the user submits their OTP. Here’s how it works:
Retrieve the OTP: The view extracts the OTP from the request data.
Find the user: It looks up the user in the database using the provided OTP.
Verify the OTP: If the user is found, it checks whether the OTP is correct and valid (not expired).
Log the user In: If the OTP is valid, it logs the user in and returns an access token.
Respond: If the OTP is invalid, it returns an error message.
Here’s an example of the code in ‘views.py’:
class VerifyOTPView(APIView):
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
otp = request.data['otp']
print(otp)
user = User.objects.get(otp=otp)
if user:
login(request, user)
user.otp = None
user.otp_expiry = None
user.max_otp_try = 3
user.otp_max_out = None
user.save()
refresh = RefreshToken.for_user(user)
return Response({'access': str(refresh.access_token)}, status=status.HTTP_200_OK)
else:
return Response("Please enter the correct OTP", status=status.HTTP_400_BAD_REQUEST)
Connecting the Views with URL Routes
To connect the ‘LoginView’ and ‘VerifyOTPView’ in your Django application, you need to create a ‘urls.py’ file in your app directory. This file will define the URL routes that link to your views. Here’s how you can set it up:
Create the urls.py file: If you haven’t already, create a ‘urls.py’ file in your app directory (e.g., my_app).
Import necessary modules: In the ‘urls.py’ file, import the required modules and views:
from django.urls import path
from .views import LoginView, VerifyOTPView
- Define URL routes: Add URL patterns for your ‘LoginView’ and ‘VerifyOTPView’:
urlpatterns = [
path('login/', LoginView.as_view(), name='login'),
path('verify-otp/', VerifyOTPView.as_view(), name='verify-otp'),
]
- Include the app’s URL patterns in your project: In your project’s main ‘urls.py’ file (located in the root directory), you need to include your app’s URL patterns:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/auth/', include('my_app.urls')), # Add your app's URL patterns here
]
In this example, ‘api/auth/’ serves as the base URL for your app’s authentication routes.
Once you set up the ‘urls.py’ file in your app and include it in your project, your ‘LoginView’ and ‘VerifyOTPView’ will be accessible through the specified URLs. Users can use these routes to log in and verify OTPs, respectively.
Wrap Up
Let’s wrap things up! You’ve learned how to implement OTP authentication in your Django and Django REST Framework applications. By adding this layer of security, you give your users a safer and more reliable way to log in and use your app.
One big benefit of OTP authentication is that it protects user accounts even if passwords are compromised. You’re adding an extra step that makes it much harder for attackers to gain unauthorized access. This keeps your users’ data safe and boosts their trust in your app.
You’ve also seen how to integrate OTP authentication smoothly into your existing application. From generating OTPs to verifying them, each step is designed to provide a seamless experience for both you and your users.
Remember to follow best practices, such as monitoring OTP attempts and implementing expiration times. Keeping user experience in mind helps your users enjoy a hassle-free journey in your app while maintaining strong security.
So, give OTP authentication a go in your projects and watch your app’s security and user experience reach new heights! If you have questions or run into any issues, don’t hesitate to seek help you’re never alone on this coding journey.
For a full setup guide and code examples, check out the Django OTP Authentication GitHub repository. Feel free to dive into the code and make use of the resources provided there.
The article is originally published on Medium: https://medium.com/@shaikhrayyan123/login-with-otp-authentication-in-django-and-django-rest-framework-242bede750e1