How to Verify Phone Numbers in a Django Application with Twilio Verify
Time to read: 13 minutes
When building a user registration and authentication system for your web application, you run the risk of not properly detecting fake or duplicate accounts. A very effective way to reduce this risk is to require users to verify their accounts right after they register.
In this tutorial I’m going to show you how to extend the Django authentication system to include an SMS verification step in the user registration flow, using the Twilio Verify service. Among other things, you will learn how to:
- Customize the user database model
- Ask for additional information in the user registration page
- Send and check SMS verification codes with the Twilio Verify service
- Prevent access to parts of the application to users that haven’t verified their accounts
Ready to begin? Let’s go!
Prerequisites
To follow this tutorial you need the following items:
- Python 3.6 or newer. If your operating system does not provide a Python interpreter, you can go to python.org to download an installer.
- A Twilio account. If you are new to Twilio click here to create a free account now and receive $10 credit when you upgrade to a paid account. You can review the features and limitations of a free Twilio account.
- A phone with an active cellular service and SMS capability, to test the project.
Project setup
This tutorial will show you how to build this project step-by-step. If you prefer to download or clone the complete project instead, see the django-verify repository on GitHub.
In this section you are going to set up a brand new Django project. To keep things nicely organized, open a terminal or command prompt, find a suitable place and create a new directory where the project you are about to create will live:
Creating a virtual environment
Following Python best practices, you are going to create a virtual environment to install the Python dependencies needed for this project.
If you are using a Unix or Mac OS system, open a terminal and enter the following commands to create and activate your virtual environment:
If you are following the tutorial on Windows, enter the following commands in a command prompt window:
Now you are ready to install the Python dependencies used by this project:
The two Python packages that are needed by this project are:
- The Django framework, to create the web application.
- The Twilio Python Helper library, to work with the Twilio Verify service.
- The python-dotenv package, to import configuration values as environment variables.
Creating a Django project
In this step you are going to create a brand new Django web application. Enter the following commands in the same terminal you used to create and activate the virtual environment:
The first command above creates a Django project and puts the project configuration files in the config
subdirectory. The next command defines a Django application called core
. After you run this second command, you will also see a subdirectory with that name added to the project. This is where you will build the logic that registers, logs in, and verifies users.
In the previous section, the python-dotenv package was installed. This package is very useful to import configuration information into environment variables. To integrate it with the Django application, add it to the manage.py file, as shown below:
With this addition, the Django application will automatically import configuration variables from a file named .env located in the project directory. This file does not exist yet, but you will be adding it in a moment.
Twilio Verify service configuration
You are now going to configure the Verify service in your Twilio account. You will need this service near the end of the tutorial, but it is best to get this done now and have it out of the way.
Log in to your Twilio Console and type Verify in the search box. Once in the Verify section of the console, click on Services. This page shows the list of verification services currently in use. In the context of Twilio Verify, a “service” is an entity that represents an application that requires verification. Click the “Create Service Now” button or the blue “+” button to create a new service, and then enter a friendly name for it. For example, django-verify.
Note that the name that you choose for your service will appear in the SMS messages that are sent to users when they are verified.
The Verify service that you create will be assigned a “Service SID” and shown in the General Settings page for the service:
Copy the SID assigned to the Verify service to the clipboard, and then paste it into a new file called .env (note the leading dot) as follows:
Replace XXXXX
with your assigned SID.
In addition to the Service SID, to work with Verify you also need your Twilio account SID and Auth Token, which you can get from the Twilio Console page:
Open the .env file again and add two more variables as shown below:
I suggest that you go over all the configuration settings for your Verify service. On this page you can change the length of the numeric codes, which delivery channels you want to use, and more.
Adding a homepage
To make sure that everything is in order, you are going to add a homepage to this project. The view is going to be called index
, and will simply render a template. Enter the following code in core/views.py:
The templates of this application are going to be stored in a templates subdirectory. Create this directory now:
Django needs to be configured to look for templates in this directory. Open file config/settings.py, find the TEMPLATES
dictionary, and edit the DIRS
key as follows:
Next, you’ll create a base template that will define the page layout of the application. Create a file named base.html in the templates directory and enter the following HTML code in it:
Now create the homepage HTML template in a file called index.html, also located in the templates folder:
The core
application needs to define its list of public URLs (only one so far). Create a file named core/urls.py and enter the following code in it:
The URL definition above needs to be added to the project-wide URL configuration. Open file config/urls.py and enter the following definitions:
Running the application
A very basic version of the Django application is now ready to be tested. Go back to your terminal and start the web server with the following command:
The output of this command will include some warnings in red regarding database migrations that haven’t been applied. Since the application does not need a database yet, it is safe to ignore this error for now. The database will be created in the next section and at that point, this warning will go away.
Type http://localhost:8000 into the address bar of your web browser to confirm that your Django project is up and running. This is what you should see:
User model
The default User
model provided by Django has the expected username
and password
fields already defined, but in this project users will need to provide their phone number for verification purposes. For that reason, the user model needs to be customized.
Open the file core/models.py and enter the following code in it:
This is an extended User
model that inherits from the default base user implemented by Django. The two fields in this model are added to those that exist in the base class. The phone
field is going to store the phone number provided by the user, and the is_verified
field is going to be useful for the application to check the verification status.
Now we need to tell Django that we want to use this model instead of the default one. Open config/settings.py, find the INSTALLED_APPS
dictionary, and add the core
application as a last element:
Then scroll down to the end of the file and add the following variable:
Save the settings file. The last step is to generate a database migration to add the changes in the new User
model to the application’s database:
The makemigrations
command looks at the model definitions in the application and generates a database migration script that includes the new User
model we created above. The migrate
command then initializes the Django database by applying all the database migrations.
If you are still running the Django web server from the previous section, stop it by pressing Ctrl-C and then restart it. From now on and until the end of this tutorial, you can leave the Django web server running while you continue working. Every time you make edits to the source code, the server will automatically update itself and restart to incorporate the changes.
Registration page
The user model is now configured to accept a phone number in addition to the standard fields required by the Django authentication system. To request the phone number from the user during registration, the registration page needs to be customized.
Let’s begin by writing the user registration view. Open core/views.py in your text editor or IDE and add the register
view:
The registration view works with a form object of class UserCreationForm
. This view will be called as a GET
request when the registration page needs to be displayed, and as a POST
request when the user is submitting their information through the form.
In the GET
request case an empty form object is created and rendered to the page, through the register.html template. For the POST
request, the form is validated, the user’s information is saved to the database, and finally the user is redirected to the index page.
This view uses two things that do not exist yet: the form class and the HTML template. You’ll define the form class in a new file you’ll need to create called core/forms.py:
There is no need to create a full form for this class, because Django’s auth
module provides a user registration form that just needs the phone number added. So for this form, the UserCreationForm
from Django is used as a base class, and only the phone
field is defined as an extension to the default fields. The form class needs to include a reference to the user model class, and the complete list of fields that will be presented to the user.
The last element that is necessary to complete the user registration flow is to create the HTML template that will render the page in the browser. Create a templates/register.html file and copy the following contents to it:
To make the registration page part of the application, add a /register/ URL to the core
application. This is the updated core/urls.py file:
Make sure the Django web server is running and then open http://localhost:8000/register/ in your web browser to see the registration page in action:
This is great, right? The styling is simple because no CSS classes have been added, but Django provides a very robust registration page, to which this project only adds the phone
field.
Want to know the best thing about this form? It is already hooked up with the database to register new users, thanks to all the logic inherited from the Django base form class. If you fill out the form and press the “Register” button, the new user will be added to the database. But keep in mind that after the user form submission is accepted and saved, the user is redirected to the index page, which currently doesn’t have any concept of a logged in user. In the next section you’ll add the login and logout flows.
Login and Logout pages
To be able to work with logged in users the application needs to have content that is only available after the user logs in. With the next change, you’ll protect the index page so that it redirects unauthenticated users to a login page. Below you can see an updated core/views.py with the changes highlighted:
As you can see, protecting a view so that only logged in users can access it just requires adding the login_required
decorator, which comes built-in with Django. This decorator will let users that are logged in access the view, but will redirect users who are not logged in to a login page. The URL of the login page is configured in config/settings.py. Add the following line at the bottom of the file:
The index.html template can now be expanded to show the username of the logged in user with a button to log out of the application. Here is the updated template:
The login and logout views are provided by Django. All that is required is to define URLs for these two views. Here is the updated core/urls.py:
The two new views need templates, given in the template_name
arguments. Create a new file called templates/login.html. This template receives a form
variable set to the login form and renders it to the page:
Note that as a nice touch, the login page includes a link to the registration page built earlier.
The templates/logout.html template just tells the user that they have been logged out, and offers a link to the index page. Create this file, then paste the following code into it:
With these changes, a basic user registration and login system is now in place. Ready to try it out? Make sure the Django web server is running, and then open a browser tab on the http://localhost:8000 URL. Since this URL is now protected by the login_required
decorator, Django will send you to the login page, where you can enter your username and password to gain access. If you haven’t registered an account yet, click on the “Register” link to do it now.
Phone verification
The project is now able to register, login, and logout users. The final part of this tutorial is to add the SMS verification as an additional step right after registering. Registered users will be able to log in and out of the application, but they won’t be able to access the index page until they verify their accounts.
Integration with Twilio Verify
The most convenient way to integrate the Django application with Twilio Verify is to create a verify.py module in the core
directory that implements the two functions of the service needed by the application. The first function sends a verification code to the user, while the second function checks a verification code once the user provides it back to the application.
Here is the core/verify.py module:
The two functions that the application will use to interface with the Verify service are going to be send()
and check()
.
Sending a verification code
To send a verification code to the user, the application needs to call the send()
function defined above and pass the phone number of the user as the only argument. This can be done in the registration view, immediately after the user is saved to the database. Here is the updated code for core/views.py:
With this small addition, each time a user is registered, a code will be sent by SMS to the phone number entered during registration. Here is the code that I received when I registered a new user with my phone number:
Twilio likes to see phone numbers given in E.164 format, which includes a plus sign prefix and the country code. As an example, a number from the United States would be given as +1AAABBBCCCC’, where
AAA is the area code and
BBB-CCCC` is the local number. In a real world application, the phone field in the form will ensure that the number is formatted correctly. In this example application you’ll have to enter the number in the correct format.
If you are using a Twilio trial account, remember that Twilio only sends SMS to phone numbers that are verified on the account. This restriction does not exist in paid accounts.
Accepting a verification code from the user
The user is now receiving a code by SMS, so the next step is to create a route that the user can use to input this number to get it verified. Add the following function to the core/views.py file:
The verify_code
view will display a VerifyForm
when it executes as a GET
request, and will accept a code from the user when executing as a POST
request. In the latter case, the code that is received from the user is sent to the verify.check()
function, and if the verification succeeds, the user model’s is_verified
attribute is set to True
and saved.
Add the form class used by this view at the bottom of core/forms.py module:
This form has just one field, where the user enters the numeric code received by SMS.
The form will be rendered as part of an HTML page. Create a file called templates/verify.html which defines the layout of this page:
Finally, a URL needs to be defined for this new view. Below is the updated core/urls.py file:
With the application in its current state you can already check how the verification codes work. Navigate to http://localhost:8000/register/ on your browser and create a new user account, using your phone number in the E.164 format. Once the registration is complete you will receive an SMS with the numeric code that verifies your new account.
Now navigate to http://localhost:8000/verify/, log into the application with the new account, and finally enter the verification code. If you enter an invalid code, the form will display again. Once you enter the correct code you will be redirected to the index page.
Preventing access to unverified accounts
The verification solution is working great, but it is not integrated into the registration flow yet. In this section you are going to add the final bit of functionality to create a seamless experience for the new user.
You’ve seen Django’s login_required
decorator, which prevents access to a page when the user is not logged in. Now you will create a second decorator called verification_required
that will work in a similar way, but will redirect the user to the verification page when the account hasn’t been verified yet. Enter the following code in a new file called core/decorators.py:
This decorator uses the same logic that login_required
uses, but it checks the is_verified
attribute of the user instead of is_authenticated
, and redirects to the /verify/ page instead of /login/.
Add the new decorator to the index()` function in core/views.py:
With this small change, access to the index page is now prevented if the user’s is_verified
attribute is False
. When the user logs in and is redirected to this page, the decorator will intercept the request and instead redirect to the verification page, giving the user a chance to verify the account before they are allowed to access the page.
Defining the login_required
and verification_required
decorators separately gives the application control over what pages only require a login and what pages require the account to be verified.
Next steps
I hope you found this tutorial useful! If you are looking for ideas on how to extend the project, here are some:
- Add an option to resend the verification code. This can be a very useful addition, because Twilio Verify codes expire after 10 minutes.
- Change the project to require a phone verification step after each log in instead of only after the user registers a new account. This is essentially a form of two-factor authentication (2FA).
- Give the user the option to receive the code via phone call.
To learn about all the Twilio Verify service has to offer, check out the documentation.
I’d love to see what you build with Django and Twilio Verify!
Miguel Grinberg is a Principal Software Engineer for Technical Content at Twilio. Reach out to him at mgrinberg [at] twilio [dot] com if you have a cool project you’d like to share on this blog!
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.