How to Enable Two-Factor Authentication (2FA) in Django

Two-Factor Authentication (2FA) is a security feature that adds an extra layer of protection to user accounts. It requires users to provide two different authentication factors: something they know (e.g., a password) and something they have (e.g., a mobile device or authentication app). In this blog post, we’ll explore how to enable 2FA in a Django web application with a complete working example.

Prerequisites:

Before implementing 2FA in your Django application, ensure you have the following components installed:

  1. Django: A Python web framework.
  2. django-otp: A library for handling one-time passwords.
  3. django-otp-plugins: Provides various 2FA plugins.
  4. django-user-agents: Helps identify the user’s device.

Step-by-Step Guide

Before diving in this, setup Authentication System using our blog – How to Create Signup, Login, and Logout Functionality in Django

Step 1: Create a Django Project and App

If you haven’t already, create a new Django project and app using the following commands:

django-admin startproject project_name
cd project_name
python manage.py startapp app_name

Step 2: Install Required Packages

Install the necessary packages using pip:

pip install django-otp django-otp-plugins django-user-agents

Step 3: Configure Settings

In your project’s settings (settings.py), add the following:

INSTALLED_APPS = [
    # ...
    'otp',
    'otp.plugins.otp_totp',
    'otp.plugins.otp_static',
    'django_otp',
    'django_otp.plugins.otp_totp',
    'django_otp.plugins.otp_static',
    'django_user_agents',
]

AUTHENTICATION_BACKENDS = (
    # ...
    'django_otp.backends.OTPAuthenticationBackend',
)

OTP_TOTP_ISSUER = 'YourAppName'

In the Django settings, the OTP_TOTP_ISSUER variable is used to define the issuer label for Time-Based One-Time Passwords (TOTP) generated in the Two-Factor Authentication (2FA) setup. It helps identify the source or organization that issued the TOTP code, and this label is often displayed to the user when they are configuring their 2FA app (such as Google Authenticator or Authy).

Here’s what it does:

  1. Issuer Label: The OTP_TOTP_ISSUER variable typically contains the name or label of your application or service. For example, if your web application is named “SecureApp,” you might set OTP_TOTP_ISSUER as follows:
   OTP_TOTP_ISSUER = 'SecureApp'
  1. User Recognition: When a user adds a TOTP token to their authentication app, the issuer label helps them recognize which service or application the token is associated with. This is especially useful when users have multiple TOTP tokens from different sources in their authentication app.
  2. QR Code Generation: If you’re using a QR code library to generate QR codes for setting up 2FA, the OTP_TOTP_ISSUER is often used as the issuer parameter in the QR code generation process. The user’s authentication app reads this information from the QR code and displays it to the user.

In practice, when a user sets up 2FA, they might see something like “SecureApp” in their authentication app alongside the generated TOTP code, making it clear which service or application the code is associated with. It enhances the user experience and helps prevent confusion, especially if the user is managing multiple 2FA tokens.

Step 4: Create a Model for User Profiles

In your app’s models.py, create a UserProfile model to store user-specific 2FA information:

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

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    devices = models.ManyToManyField(TOTPDevice)

Certainly, let’s dive into step 5 and 6 in more detail.

Step 5: Create Views and Templates

In this step, we’ll create views and templates for enabling and verifying Two-Factor Authentication (2FA).

Enabling 2FA View:

  1. View: Create a view that allows users to enable 2FA for their accounts. You can use Django’s class-based views for this purpose. Below is a basic example:
# views.py

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django_otp.plugins.otp_totp.models import TOTPDevice
from .forms import Enable2FAForm

@login_required
def enable_2fa(request):
    if request.method == 'POST':
        form = Enable2FAForm(request.user, request.POST)
        if form.is_valid():
            # Enable 2FA for the user
            device = TOTPDevice.objects.create(user=request.user)
            device.save()
            return redirect('verify_2fa')
    else:
        form = Enable2FAForm(request.user)

    return render(request, 'enable_2fa.html', {'form': form})
  1. Template: Create an HTML template (enable_2fa.html) to display the 2FA setup form. This template should include fields for users to set up 2FA, like scanning a QR code or entering a verification code from their authentication app.

Verifying 2FA View:

  1. View: Create a view for verifying 2FA during the login process. This view will prompt users to enter the 2FA code generated by their authentication app.
# views.py

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_totp.views import TOTPVerificationView

@login_required
def verify_2fa(request):
    if request.method == 'POST':
        # Handle the 2FA verification form submission
        return TOTPVerificationView.as_view()(request)

    devices = TOTPDevice.objects.filter(user=request.user)
    return render(request, 'verify_2fa.html', {'devices': devices})
  1. Template: Create an HTML template (verify_2fa.html) to display the 2FA verification form. This template should include a field for users to enter their 2FA code, as well as a list of their registered 2FA devices for management.
{% extends 'base.html' %}

{% block content %}
  <h2>Enable Two-Factor Authentication (2FA)</h2>
  <p>Follow these steps to enable 2FA for your account:</p>

  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Enable 2FA</button>
  </form>
  
  <a href="{% url 'verify_2fa' %}">Already enabled 2FA? Verify it here.</a>
{% endblock %}

Step 6: Update User Authentication Views

To make use of 2FA, you need to update your user authentication views, such as the login view. Here’s an example of how you can modify the login view to incorporate 2FA verification:

# views.py

from django.contrib.auth.views import LoginView
from django_otp.plugins.otp_totp.models import TOTPDevice

class CustomLoginView(LoginView):
    template_name = 'login.html'

    def form_valid(self, form):
        # Check if the user has 2FA enabled
        user = self.request.user
        if TOTPDevice.objects.filter(user=user).count() > 0:
            # Redirect to the 2FA verification view
            return redirect('verify_2fa')

        # Continue with regular login
        return super().form_valid(form)

In this example, if the user has 2FA devices registered, they are redirected to the 2FA verification view (verify_2fa). Otherwise, they proceed with the regular login process.

{% extends 'base.html' %}

{% block content %}
  <h2>Verify Two-Factor Authentication (2FA)</h2>
  <p>Enter the verification code from your authentication app:</p>

  <form method="post">
    {% csrf_token %}
    <label for="verification_code">Verification Code:</label>
    <input type="text" id="verification_code" name="verification_code" required>
    <button type="submit" class="btn btn-primary">Verify</button>
  </form>

  <h3>Registered 2FA Devices:</h3>
  <ul>
    {% for device in devices %}
      <li>{{ device.name }}</li>
    {% empty %}
      <li>No 2FA devices registered.</li>
    {% endfor %}
  </ul>
  
  <a href="{% url 'enable_2fa' %}">Enable 2FA on another device</a>
{% endblock %}

Remember to update your login template (login.html) to include a 2FA option if the user has 2FA enabled and to submit the 2FA code when necessary

Step 7: Implement Middleware (Optional)

Optionally, you can implement middleware to detect user agents and prompt for 2FA verification when a user logs in from an unrecognized device.

Step 8: Migrate and Run the Server

Run migrations to create the necessary database tables:

python manage.py makemigrations
python manage.py migrate

Start your Django development server:

python manage.py runserver

Conclusion:

Implementing Two-Factor Authentication (2FA) in Django enhances the security of your web application by adding an extra layer of protection to user accounts. By following the steps outlined in this guide and customizing them to fit your application”s specific requirements, you can enable 2FA effectively. Remember to provide clear instructions to your users on how to enable and use 2FA to ensure a secure login experience.

Blogs You Might Like to Read!