Omonbude Emmanuel
Backend Engineer
Fullstack Web Developer
Mobile Application Developer
  • Residence:
  • Phone:
  • Email:

Implementing Two-Factor Authentication with Google Authenticator in Django

Omonbude Emmanuel | Feb. 21, 2024, 3:14 p.m.



Two-factor authentication adds an extra layer of security to your Django application by requiring users to provide two forms of identification before granting access.

In this tutorial, we'll focus on using Google Authenticator as the second factor in our two-factor authentication setup. Google Authenticator is a popular choice for 2FA because it's easy to use, supports multiple accounts, and generates time-based one-time passwords (TOTPs) that are valid for a short period of time.
By the end of this tutorial, you'll have a solid understanding of how to implement two-factor authentication with Google Authenticator in your Django application, helping to keep your users' accounts safe and secure.



  • Basic understanding of Python and Django: You should be comfortable with the Python programming language and have a basic understanding of Django, including models, views, templates, and URLs.
  • Familiarity with Google Authenticator: It's helpful to have some knowledge of how Google Authenticator works, including how to generate and use time-based one-time passwords (TOTPs).
  • Google Authenticator app: To test the two-factor authentication process, you'll need to install the Google Authenticator app on your mobile device or use a similar TOTP-based authentication app. You can download it from apple store or playstore.
  • Python and django





We start by creating a new django project that we'd be using for this tutorial

django-admin startproject authenticator



We need the django-otp package to be able to genetare TOTP and verify as well for the project.

pip install django-otp




After the installation, we can proceed to create the user app

python3 startapp user


User Registration

At this point, we need to create our registration and login page so that users can be able to register and login in the project.


Create Custom User in user/ 
Paste the code below

from django.db import models
from django.contrib.auth.models import AbstractUser
from django_otp.plugins.otp_totp.models import TOTPDevice

class CustomUser(AbstractUser):
    fullname = models.CharField(max_length=50, default='')
    email = models.EmailField(unique=True)
    authenticator_secret = models.TextField(blank=True, null=True)
    def __str__(self):

Create file in user folder and paste the following code


from django import forms
from django.core.exceptions import ValidationError
from .models import CustomUser
from django.contrib.auth import authenticate

class RegistrationForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput)
    confirm_password = forms.CharField(widget=forms.PasswordInput)

    class Meta:
        model = CustomUser
        fields = ['fullname', 'email', 'username', 'password']

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')

        if password != confirm_password:
            raise ValidationError('Passwords do not match')

        return cleaned_data


The RegistrationForm is used to register our users and validate their details as well.

paste the code below in the

from django.contrib.auth import authenticate, login, logout, get_user_model
from django.contrib import messages
from user.forms import LoginForm, RegistrationForm

def homeView(request):
    return render(request, 'index.html')

def signupView(request):
    form = RegistrationForm() 
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        password = request.POST['password']
        if form.is_valid():
            user =  
            messages.success(request, 'Registration successful')            
            return redirect('userurl:index')
    return render(request, 'signup.html', {'form':form})

We have a function based view to display our home page and signup page. The function also handles user registration as well.

Adding Templates

Create a folder called templates in the project folder (authenticator/templates)
Create two files named message.html, index.html and signup.html
paste the code below to signup.html



    <h3 >Register An Account</h3>
    <form  action="{% url 'userurl:signup' %}" method="post"> {% csrf_token %}
        {{ form.as_p }}
        {% include 'messages.html' %}

        <button type="submit">Signup</button>
        <p >Already registered? <a href="">Login here</a></p>


Paste the code below to index.html


{% if request.user.is_authenticated %}
<p>Hello, {{ request.user.username }}</p>
<a href=""><button>Logout</button></a>

{% endif %}

{% include 'messages.html' %}

    <li><a href="">Login</a></li>
    <li><a href="{% url 'userurl:signup' %}">Register</a></li>


Finally, paste the code below to message.html


{% if messages %}
{% for message in messages %}
<div  style="color: {% if message.tags == 'error' %}red {% else %} green{% endif %}">
        {{ message }}
{% endfor %}
{% endif %}


message.html is used to display message if we have any django messages. It will be included in other files

Create a file in user folder and paste the code below


from django.urls import path
from . import views
from .views import *
urlpatterns = [
    path('', views.homeView, name='index'),
    path('signup/', views.signupView, name='signup')



Update Project url

Go to the project file (authenticator/ and paste the code below



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

urlpatterns = [
    path('', include('user.urls', namespace='user')),


Update the

Paste the following under installed apps




We have to add our user app to and add out django_otp plugin too


add the code below in the 

AUTH_USER_MODEL = 'user.CustomUser'

When you set AUTH_USER_MODEL = 'user.CustomUser', you're telling Django to use a custom user model called CustomUser that is defined in the user app of your project.
Django will use this model instead of the built-in User model for authentication and user management.


Update the templates section and add the code below

'DIRS': [os.path.join(BASE_DIR, 'templates')],


Make Migrations

Run the migration commands to makemigration and migrate our tables to our database


python3 makemigrations user

python3 migrate



Run the server command to start up our development server


python3 runserver


Open your browser and paste the local server url

Register a user and make sure its successful.


Adding Google Authenticator on sign in

Upon user sign-in, our system will automatically generate a unique QR code, enabling the user to seamlessly register their account on the Authenticator app. This QR code serves as a secure means of verifying the login process, enhancing overall account security.


edit the user/ and add the code below


from django_otp.plugins.otp_totp.models import TOTPDevice #import form the top of your code

class EmailTOTPDevice(TOTPDevice, models.Model):
    email = models.EmailField(unique=True)   
    def __str__(self):


Create a folder called helpers and under this folder, create a file called

Paste the following code under this file



import re
from user.models import EmailTOTPDevice
from django.contrib.auth import get_user_model
User = get_user_model()

def generate_totp_qr_code(email):
    user = User.objects.get(email__iexact=email)
        totp_device = EmailTOTPDevice.objects.get(, user=user)
    except EmailTOTPDevice.DoesNotExist:
        totp_device = EmailTOTPDevice.objects.create(, tolerance=0, user=user)
        #tolerance is set to 0 because we do not want to accept codes that have passed 30 seconds 
    name = f'Django Auth: {email}'
    modified_otp_uri = re.sub(r'otpauth://totp/[^?]+', f'otpauth://totp/{name}', totp_device.config_url)
    return modified_otp_uri

def verify_otp(email, code):
    user = User.objects.get(email__iexact=email)
    totp_device = EmailTOTPDevice.objects.get(, user=user)
    return  totp_device.verify_token(code)

def extract_secret(uri):
    secret_match ="secret=(.*?)(&|$)", uri)
    secret =
    return secret



1. generate_totp_qr_code(email):

  • This function takes an email address as input and generates a QR code for setting up 2FA for that user.
  • It first retrieves the user object using the email address.
  • It then tries to get an existing EmailTOTPDevice object associated with the user. If it doesn't exist, it creates a new one with tolerance set to 0 (meaning only valid within 30 seconds).
  • It constructs a name for the QR code entry and modifies the original configuration URL to include this name.
  • The extract_secret function (not shown) extracts the secret key from the modified URL.
  • Finally, the modified URL is returned, which can be used to generate a QR code for the user to scan and set up 2FA.

2. verify_otp(email, code):

  • This function takes an email address and an OTP code as input and verifies if the code is valid for that user.
  • It retrieves the user object and the corresponding EmailTOTPDevice object.
  • It then calls the verify_token method on the device object, passing the provided OTP code.
  • The verify_token method checks if the code is valid for the device and within the allowed time window.
  • The function returns True if the code is valid, False otherwise.

3. extract_secret(uri):

  • This function, extracts the secret key from a provided URI string.
  • It uses regular expressions to find the part of the URI containing the secret key.
  • It extracts the secret key and returns it.



Add the code below to


class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)
    def clean(self):
        cleaned_data = super().clean()
        username = cleaned_data.get('username')
        password = cleaned_data.get('password')

        if username and password:
            user = authenticate(username=username, password=password)
            if not user or not user.is_active:
                raise forms.ValidationError('Invalid username or password.')
            return cleaned_data

This form is used to validate and login our user.


We have to update our now so that our users can be able to login and verify TOTP
Paste the following code in your


from django.shortcuts import redirect, render
from django.contrib.auth import authenticate, login, logout, get_user_model
from django.contrib import messages
from helpers.generate_otp import extract_secret, generate_totp_qr_code, verify_otp
from user.forms import LoginForm, RegistrationForm
#imports should be at the top of your code


def loginView(request):    
    form = LoginForm()
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            print(username, password, "printed..")
            user = authenticate(username=username, password=password)

            if user is not None:
                secret = ''
                secret_stored = False 
                #used to know if the secret has been stored before or not so that we can display the qr code in frontend or not
                if user.authenticator_secret==None or user.authenticator_secret == '':                
                    qs = generate_totp_qr_code(
                    secret = extract_secret(qs)
                    secret_stored = True               
                return render(request, 'verify.html', {'qs':qs, 'email', 'secret_stored':secret_stored, 'secret':secret})

    return render(request, 'signin.html', {'form':form})

def verifyOtp(request):
    if request.method == 'POST':
        otpcode = request.POST.get('otp')
        email = request.POST.get('email')        
        verify  = verify_otp(email, otpcode)
        if verify == False:
            messages.error(request, 'Invalid Code')
            return redirect('userurl:login')                
        user = User.objects.get(email__iexact=email)
        login(request, user)   
        if user.authenticator_secret==None or user.authenticator_secret == '':                
            qs = generate_totp_qr_code(
            secret = extract_secret(qs)
            user.authenticator_secret = secret 
        messages.success(request, 'Authentication Successful')     
        return redirect('userurl:index')        
    return redirect('userurl:login')        
def logoutUser(request):
    return redirect('/login/')


We proceed to update our
Add the following codes to your


    path('login/', views.loginView, name='login'),
    path('verify-otp/', views.verifyOtp, name='verify_otp'),
    path('logout/', views.logoutUser, name='logout'),


Create signin.html and verify.html files in the template folder to display the signin and verify page
Paste the following code inside the signin.html

<div >
    <h3 >Login Your Account</h3>
    <form class="form-signup" action="{% url 'userurl:login' %}" method="post"> {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" >Log In</button>
        {% include 'messages.html' %}
        <p >Don't have an account?  <a href="{% url 'userurl:signup' %}">Signup here</a></p>


 Paste the code below to verify.html



        <h3>Verify OTP</h3>
        <p>Verify your login with google authenticator</p>
        <form method="post" action="{% url 'userurl:verify_otp' %}"> {% csrf_token %}
                    {% if secret_stored == False %}
                    <div id="qrcode" style="width: 250px; height:250px;"></div>
                        <p>Key: {{secret}}</p>
                    {% endif %}
                    <input type="hidden" name="email" value="{{email}}">
                    <input name="otp" type="text" placeholder="OTP" class="form-control required name" required>
            <button type="submit" >Submit</button>
            {% include 'messages.html' %}
<script src=""></script>

    var qrCodeData = "{{ qs }}"; 
    var qrCodeOptions = {
        width: 200,
        height: 200,        
        colorLight: "#ffffff",
        correctLevel: QRCode.CorrectLevel.H,
    var qrCode = new QRCode(document.getElementById("qrcode"), qrCodeData, qrCodeOptions);


Update the urls in the index.html page to our app urls

go to your browser and paste 

Proceed to login, and verify with the authenticator app




In conclusion, implementing 2FA with Google Authenticator in your Django application is a wise investment in user security. By following the guidelines outlined in this article and tailoring them to your specific needs, you can provide a strong authentication mechanism that fosters trust and protects user data. Remember, security is an ongoing process, and staying informed about updates and best practices is key to maintaining a secure environment for your users.

© 2023 Omonbude Emmanuel

Omonbude Emmanuel