OTP Verification in Django

When I started building web applications, user registration felt easy, just a form, some validations, and done. But as security concerns grew and user experience expectations changed, I realized adding an OTP (One-Time Password) verification step is essential. It not only enhances security but also improves trustworthiness, especially when dealing with sensitive data like emails or phone numbers.

In this article, I’ll walk you through how to implement user registration with OTP verification in Django, based on my years of experience. I’ll provide clear, practical examples tailored for developers in the USA, so you can build secure and reliable Django apps quickly.

Let’s get in!

Method for OTP Verification in User Registration

You might wonder, why to add OTP verification? In the US market, where data privacy laws and cybersecurity awareness are high, verifying users’ contact info is crucial. OTP helps:

  • Confirm that the user owns the email or phone number they provide.
  • Reduce fake or spam registrations.
  • Add an extra layer of security during sign-up.

Implementing this in Django is easier than you might think, and I’ll show you two methods: using email-based OTP and SMS-based OTP, so you can pick what fits your project.

Read Check if Python Dictionary is Empty

Method 1: Email-Based OTP Verification in Django

Email OTP is the most common approach. Here’s how I usually set it up.

Step 1: Set Up Your Django Project

First, create a new Django project and app:

django-admin startproject otp_project
cd otp_project
python manage.py startapp accounts

Add accounts to your INSTALLED_APPS in settings.py.

Step 2: Configure Email Backend

For development, I use the console email backend to see OTPs in the terminal:

# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

For production, configure SMTP settings with services like SendGrid or Amazon SES.

Step 3: Create User Registration Model and Form

We’ll use Django’s built-in User model, but create a profile to store OTP and verification status.

# accounts/models.py
from django.contrib.auth.models import User
from django.db import models
import random

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    otp = models.CharField(max_length=6, blank=True, null=True)
    is_verified = models.BooleanField(default=False)

    def generate_otp(self):
        self.otp = f"{random.randint(100000, 999999)}"
        self.save()

Create signals to auto-create UserProfile when a User is created:

# accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

Register signals in apps.py:

# accounts/apps.py
from django.apps import AppConfig

class AccountsConfig(AppConfig):
    name = 'accounts'

    def ready(self):
        import accounts.signals

Create a registration form:

# accounts/forms.py
from django import forms
from django.contrib.auth.models import User

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

    class Meta:
        model = User
        fields = ['username', 'email', '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 forms.ValidationError("Passwords do not match.")

Step 4: Create Views for Registration and OTP Verification

# accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from django.core.mail import send_mail
from .forms import UserRegistrationForm
from .models import UserProfile

def register(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)
            user.set_password(form.cleaned_data['password'])
            user.is_active = False  # Deactivate account until verified
            user.save()

            profile = user.userprofile
            profile.generate_otp()

            send_mail(
                'Your OTP Code',
                f'Your OTP is {profile.otp}',
                'no-reply@yourdomain.com',
                [user.email],
                fail_silently=False,
            )
            request.session['user_id'] = user.id
            return redirect('verify_otp')
    else:
        form = UserRegistrationForm()
    return render(request, 'accounts/register.html', {'form': form})

def verify_otp(request):
    user_id = request.session.get('user_id')
    if not user_id:
        return redirect('register')

    user = User.objects.get(id=user_id)
    profile = user.userprofile

    if request.method == 'POST':
        input_otp = request.POST.get('otp')
        if input_otp == profile.otp:
            user.is_active = True
            user.save()
            profile.is_verified = True
            profile.otp = ''
            profile.save()
            del request.session['user_id']
            return redirect('login')
        else:
            error = "Invalid OTP. Please try again."
            return render(request, 'accounts/verify_otp.html', {'error': error})
    return render(request, 'accounts/verify_otp.html')

Step 5: Add URLs and Templates

# accounts/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('register/', views.register, name='register'),
    path('verify-otp/', views.verify_otp, name='verify_otp'),
]

Include these URLs in your project’s urls.py.

Create simple HTML templates for registration and OTP verification with forms.

You can see the output in the screenshot below.

django otp
django-otp

Check out Python Filter Not in Django

Method 2: SMS-Based OTP Verification Using Twilio

For US-based users, SMS OTP is highly effective. Here’s a quick overview of how I integrated Twilio for SMS OTP verification.

Step 1: Install Twilio

pip install twilio

Step 2: Update the UserProfile Model to Store Phone Number

Add a phone number field and OTP logic similar to the email method.

Step 3: Configure Twilio Client

# accounts/utils.py
from twilio.rest import Client

def send_otp_via_sms(phone_number, otp):
    account_sid = 'your_account_sid'
    auth_token = 'your_auth_token'
    client = Client(account_sid, auth_token)

    message = client.messages.create(
        body=f"Your OTP code is {otp}",
        from_='+1234567890',  # Your Twilio number
        to=phone_number
    )
    return message.sid

Step 4: Modify Registration and OTP Verification Views

Send OTP via SMS using the send_otp_via_sms function instead of email.

Read Union Operation on Django Models

Tips from My Experience

  • Always set user.is_active = False until verification to prevent unauthorized access.
  • Store OTPs securely and clear them after verification.
  • Use session or temporary storage to track users between registration and OTP verification.
  • For production, use reliable email/SMS services with proper error handling.
  • Consider adding OTP expiration for better security.

Implementing OTP verification in Django might seem complex at first, but breaking it down into manageable steps makes it straightforward. Whether you choose email or SMS OTP, the key is to ensure a smooth user experience while maintaining security.

If you want reusable code and a clean structure, consider creating Django custom user models and integrating third-party packages like django-otp or django-allauth for extended features.

I hope this guide makes your Django user registration more secure and trustworthy!

You may read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.