Enable Multiple OTP Methods in your Django Application with Twilio

August 03, 2022
Written by
Ath Tripathi
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Mia Adjei
Twilion

Enable Multiple OTP Methods in your Django Application with Twilio

In this tutorial, you will learn how to make a one-time password (OTP) verification system in Django using Twilio. This application will allow users to select their preferred method of receiving the OTP.

We will be using the Twilio SMS service and Twilio WhatsApp sandbox to deliver OTPs through SMS and WhatsApp channels to users.

Let’s talk about our main focus for this tutorial.

Our primary focus will be to make a pipeline of delivering OTPs and verifying them. That’s all we are going to do today.

What are we not going to do? 

We will not make an entire Django user management and authentication system using the default Django login setup — we will be simply using cookies to see if the user is OTP verified or not.

Keeping these things in mind, let’s get started.

Prerequisites

  • Python 3.6 or above installed
  • Basic knowledge of Django (You'll have a chance to install it in a later step.)
  • A Twilio account (If you don't have one, sign up for a free account here.)
  • A phone number and WhatsApp number for testing

Setup

To start, navigate to where you would like to set up your project, and create a new directory where all the files for this project will live. Once you have done this, change into the new directory:

mkdir twiliotutorial
cd twiliotutorial

Next, you'll need to create a Python virtual environment where you can install the dependencies for this project. If you are working in a Mac or Unix environment, run the following commands:

python3 -m venv venv
source venv/bin/activate

If you are working in a Windows environment, run the commands below instead:

python -m venv venv
venv\Scripts\activate

Inside this new virtual environment, install the dependencies for this project:

pip3 install django twilio

Now that you have the requirements you need, it's time to start building our project.

Create a blank Django project

Let’s get started by creating a basic Django project with a single app. Run the following command in the terminal to create the new Django project:

django-admin startproject twiliotutorial

This will create a blank project. Now, change into to the project directory and run the following command to create a simple Django app called verification:

python manage.py startapp verification

Open twiliotutorial/settings.py and add verification to the app list:

…..
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'verification'
]
……

Now go to twiliotutorial/urls.py and include the app’s urls:

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('verification.urls'),name="verification")
]

Inside the verification folder, create a new file called urls.py and add the following code:

from django.urls import path

from . import views

urlpatterns = [
    path('', views.register, name='register'),
    path('home', views.home, name='home'),
    path('otp/<str:uid>/', views.otpVerify, name='otp')
]

We will be using three views for our project. One is for registering, which will take your username and phone number, and then it will send an OTP to your phone number.

The OTP verify endpoint is where you will be able to enter your OTP to verify it, and finally, you will be redirected to the home endpoint if you enter the correct OTP within 10 minutes.

For now go to verification/views.py and make 3 placeholder functions:

from django.http import HttpResponse
from django.shortcuts import render,redirect
from django.contrib.auth.models import User
from .models import Profile
import random
from .helper import MessageHandler

def home(request):
        pass

def register(request):
        pass

def otpVerify(request,uid):   
        pass    

Now, for the final step in setting up the Django project, let’s create a model for saving a user profile and saving the OTP in the profile. Go to verification/models.py and add this code:

from django.db import models
from django.contrib.auth.models import User
import uuid
# Create your models here.
class Profile(models.Model):
    user=models.OneToOneField(User,   on_delete=models.CASCADE,related_name="profile")
    phone_number=models.CharField(max_length=15)
    otp=models.CharField(max_length=100,null=True,blank=True)
    uid=models.CharField(default=f'{uuid.uuid4}',max_length=200)

Here we are creating a simple profile model that has a linking field with the default Django User model. There is also a field that will contain the user's phone number.

The OTP field will store the most recent OTP sent to the user associated with the profile, and the UID field will be used to get a certain profile object.

Now that you are all done with setting up the basic structure of the Django project, you can run the necessary migrations and start the server by running the following commands in your terminal:

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

Set up Twilio for SMS

If you are new to Twilio, go to https://www.twilio.com/try-twilio and create a free account if you don’t have one already.

Then verify your own phone number:

Form to verify your phone number

Choose your preferred settings:

Twilio sign-up questionnaire

Then you will see something like the following.  Click on “Get a Twilio phone number”:

Diagram showing the steps to get a Twilio phone number

Once you complete the account setup steps, you will see the following details in your Twilio Console that you are going to use in your Django project:

Twilio account info, with account SID, auth token, and Twilio phone number

Add them to the bottom of  twiliotutorial/settings.py:

ACCOUNT_SID='YOUR ACCOUNT SID'
AUTH_TOKEN='YOUR AUTH TOKEN'
COUNTRY_CODE='+country code of your choice'
TWILIO_WHATSAPP_NUMBER='whatsapp:+14155238886'
TWILIO_PHONE_NUMBER='number you get from Twilio'

In this tutorial, a country code is added to the settings so that SMS or WhatsApp messages are only sent to numbers within your selected country.

If you are interested in sending messages internationally, you can customize the code to remove references to a country code, and in the testing step later, instead have users enter their phone numbers in the international E.164 format.

Learn more about formatting international phone numbers here.

Set up Twilio for WhatsApp messaging

For WhatsApp messaging, we will need to set up a Twilio WhatsApp sandbox. It will work like this: we will be sending messages from our program to the Twilio server, and then Twilio will send messages to our registered WhatsApp number.

In the left sidebar, under the Messaging menu, go to “Try it out”, then “Send a WhatsApp message”:

Twilio Sandbox setup page

On the Twilio Sandbox page, click on the blue link that says “click here”. Then send a WhatsApp message containing the text highlighted in bold (your message may vary) to register your phone number to receive WhatsApp messages in the development environment.

WhatsApp message confirming phone number is registered

If you see this message, it means that your number is ready to receive a WhatsApp message. Keep in mind that you only need to verify numbers if you want to develop the application in the sandbox environment.

Create the connection between Django and Twilio

To create a connection, we will be using a helper file to send WhatsApp and SMS messages.

Create a new file called helper.py inside the verification folder.

We will be creating a MessageHandler class in the file, which will be responsible for sending WhatsApp and SMS. Add the code to the file as follows:

from django.conf import settings
from twilio.rest import Client


class MessageHandler:
    phone_number=None
    otp=None
    def __init__(self,phone_number,otp) -> None:
        self.phone_number=phone_number
        self.otp=otp
    def send_otp_via_message(self):     
        client= Client(settings.ACCOUNT_SID,settings.AUTH_TOKEN)
        message=client.messages.create(body=f'your otp is:{self.otp}',from_=f'{settings.TWILIO_PHONE_NUMBER}",to=f'{settings.COUNTRY_CODE}{self.phone_number}')
    def send_otp_via_whatsapp(self):     
        client= Client(settings.ACCOUNT_SID,settings.AUTH_TOKEN)
        message=client.messages.create(body=f'your otp is:{self.otp}',from_=f'{settings.TWILIO_WHATSAPP_NUMBER}',to=f'whatsapp:{settings.COUNTRY_CODE}{self.phone_number}')

You can use the above code as it is, but for your curiosity let me explain the code. We create a MessageHandler class that takes two arguments: OTP and phone_number. phone_number is the phone number to which we want to send an OTP.

We have two functions in this class: one that is responsible for sending SMS and the other for sending WhatsApp messages.

We initialize the Client in both functions first, and then we send messages using Twilio functions. Note that in the WhatsApp function, we will use a sandbox phone number instead of the number given to us by Twilio.

Create the frontend for your project

In this section, we will be creating the register and otpVerify views, and HTML files for them.

Create the register view

Let's write code for our HTML file. Inside the verification app’s folder, create a new folder with the name templates, and inside, create a file named register.html.

Add the following code:

<!DOCTYPE html>
<html>
<head>
    <title>Register</title>
    <style>
        body{
            background-color: rgb(193, 156, 228);
        }
       form {
  position: fixed; /* Stay in place */
  left: 30%;
  top: 10%;
  width: 300px; /* Full width */
  height: 350px; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(55, 56, 95); /* Fallback color */
  padding: 60px;
  color: aliceblue;
}
input[type=text], input[type=password] {
  width: 100%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
input[type=submit]{
    margin-top:50px;
    width:100px;
    height:50px;
    font-size: 20px;
    border-radius: 20px;
    background-color: rgb(33, 70, 70);
    color: wheat;
}
.radio-box{
    margin-top:-30px;
}
h2{
    margin-top: -30px;
    color: rgb(213, 217, 221);
}
    </style>
</head>
<body>
   
    <form action="/" method="post">
        <h2>REGISTRATION FORM</h2>
        {% csrf_token %}
        <label for="user_name">User name:</label>
        <input id="user_name" type="text" name="user_name">
        <br>
        <label for="phone_number">Phone Number:</label>
        <input id="phone_number" type="text" name="phone_number">
        <br>
        <h3>OTP verification method:</h3>
        <br>
        <div class="radio-box">
        <label for="methodOtpSms">SMS:</label>
        <input type="radio" name="methodOtp" id="methodOtpSms" value="methodOtpSms">

        <label for="methodOtpWhatsapp">WhatsApp:</label>
        <input type="radio" name="methodOtp" id="methodOtpWhatsapp" value="methodOtpWhatsapp">
    </div>
        <input type="submit" value="login">
        </form>
</body>
</html>

Here, a user can simply fill out the form and then submit it to send a POST request. The form allows them to enter their username and phone number in input fields, and use the radio buttons to select whether they want to receive an OTP from SMS or WhatsApp.

Now in verification/views.py, let's make the register view:

def register(request):
    if request.method=="POST":
        if User.objects.filter(username__iexact=request.POST['user_name']).exists():
            return HttpResponse("User already exists")

        user=User.objects.create(username=request.POST['user_name'])
        otp=random.randint(1000,9999)
        profile=Profile.objects.create(user=user,phone_number=request.POST['phone_number'],otp=f'{otp}')
        if request.POST['methodOtp']=="methodOtpWhatsapp":
            messagehandler=MessageHandler(request.POST['phone_number'],otp).send_otp_via_whatsapp()
        else:
            messagehandler=MessageHandler(request.POST['phone_number'],otp).send_otp_via_message()
        red=redirect(f'otp/{profile.uid}/')
        red.set_cookie("can_otp_enter",True,max_age=600)
        return red  
    return render(request, 'register.html')

Here we render the register.html on a GET request, and on a POST request, we first create a User object with the username if it does not exist already, and then we generate an OTP using the randint function.

Then we create a Profile with the user instance we created before, the phone number, and the OTP.

After that, we check what method the user chose to receive their OTP and send the OTP using the function we created before.

Finally, we set a cookie with a lifetime of 10 minutes, and we will be using this cookie to define the time limit within which we can enter the OTP.

Here how the frontend will look:

Registration form, with username, phone number, and OTP verification fields

With your server still running, you can see this form by opening http://localhost:8000/ in your browser.

Authenticate OTP and redirect to home

In this section, we are going to create the main feature — the focus of this tutorial: we are going to use straightforward logic to verify the OTP.

Create a file in the templates folder and name it otp.html.

Go to otp.html and add this code:

<!DOCTYPE html>

<html>

<head>
    <title>Register</title>
    <style>
        body{
            background-color: rgb(193, 156, 228);
        }
       form {
  position: fixed; /* Stay in place */
  left: 30%;
  top: 10%;
  width: 300px; /* Full width */
  height: 200px; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(55, 56, 95); /* Fallback color */
  padding: 60px;
  color: aliceblue;
}
input[type=text], input[type=password] {
  width: 100%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
input[type=submit]{
    margin-top:50px;
    width:100px;
    height:50px;
    font-size: 20px;
    border-radius: 20px;
    background-color: rgb(33, 70, 70);
    color: wheat;
}
.radio-box{
    margin-top:-30px;
}
h2{
    margin-top: -30px;
    margin-left: 30px;
    color: rgb(213, 217, 221);
}
    </style>
</head>
<body>
    {% block includes %}
    <form action="/otp/{{id}}/" method="post">
        <h2>OTP VERIFICATION:</h2>
        {% csrf_token %}
        <label for="otp">OTP:         </label>
        <input id="otp" type="text" name="otp">
        <input type="submit" value="login">
        </form>
        {% endblock includes %}

</body>
</html>

Here we are just taking the OTP from the user and sending it back to the otp/<uid> endpoint. Here the {{id}} will be sent from the view that we will create.

Here is how it will look:

OTP verification form

Go to verification/views.py and create the otpVerify function:

def otpVerify(request,uid):
    if request.method=="POST":
        profile=Profile.objects.get(uid=uid)     
        if request.COOKIES.get('can_otp_enter')!=None:
            if(profile.otp==request.POST['otp']):
                red=redirect("home")
                red.set_cookie('verified',True)
                return red
            return HttpResponse("wrong otp")
        return HttpResponse("10 minutes passed")        
    return render(request,"otp.html",{'id':uid})

Here we render otp.html along with uid for a GET request. For the POST request, we first check if the cookie we created is still alive. If it is not, then we will get a message that 10 minutes have already passed.

We then use theuid to get the user's profile and check if the OTP saved on the profile matches with the OTP that the user entered. If it matches, then we set cookies as verified=True and redirect the user to the home page. Otherwise, verified=False, and then the application will redirect to home.

Finally, let’s create the home view:

def home(request):
    if request.COOKIES.get('verified') and request.COOKIES.get('verified')!=None:
        return HttpResponse(" verified.")
    else:
        return HttpResponse(" Not verified.")

This view will check if the verification cookie is set to true or not and it will return a response on the basis of that.

Test your work

Make sure you have verified the numbers that you are going to use for testing. Verify your phone number here if you have not done so already.

We will be using the phone number that we used to verify our Twilio account to receive the OTP.

Here is how everything will work:

Enter a username and phone number, then select the OTP verification method.

Filled-in registration form

Once you receive the OTP on your phone, enter the number into the verification form.

OTP verification form with filled-in OTP field

After verifying the OTP, you will see a page with the HTTPResponse "verified".

Response saying "verified"

Here is the OTP we received via SMS:

Phone notification showing the OTP code in an SMS

If you select WhatsApp, the OTP will look like the following:

WhatsApp message "your otp is 1158"

Conclusion

What we learned today:

  • How to create your own verification system for a Django website
  • How to give users a choice of SMS and WhatsApp for receiving OTP
  • How to authenticate using SMS or WhatsApp in a single application
  • The pipeline of OTP authentication in Django

I am Ath Tripathi, a 16-year-old programmer from India and I have been doing programming for the last 3 years, working part-time and as a freelancer for many clients and companies. Web, app, and game development are my main interests. I do blogging as a hobby and as a freelancer.