Login with OTP via Email/Phone in Django

Adding OTP (One-Time Password) authentication via email or phone to your Django application is a valuable feature for enhancing security. In this modified version of the blog, I’ll guide you through the process of adding OTP-based login using email. You can adapt the same principles for phone-based OTP authentication as needed.

This is extended project which was created in our blog – How to Create Signup, Login, and Logout Functionality in Django

Prerequisites:

Before you begin, make sure you have the following:

  1. Django installed (as mentioned in the original blog).
  2. Django’s django-otp package installed. You can install it using pip:
   pip install django-otp

Now, let’s proceed with adding OTP-based login via email:

Step 1: Create an App

Create a new Django app for OTP-based authentication:

python manage.py startapp otp_auth

And add it to INSTALLED_APPS in settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
    'otp_auth',
]

Step 2: Model and Database Setup

In your app’s models.py, create a model to store OTP information along with the user’s email. This model will be used to generate and validate OTPs.

from django.db import models
from django.contrib.auth.models import User

class OTP(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    otp_secret = models.CharField(max_length=16)
    email = models.EmailField(unique=True)
    is_verified = models.BooleanField(default=False)

    def __str__(self):
        return self.email

from django.contrib import admin
admin.site.register(OTP)

Don’t forget to create and apply migrations:

python manage.py makemigrations
python manage.py migrate

Step 3: OTP Generation and Sending

In your app, create a view that generates and sends OTPs to users via email. You can use a library like pyotp to generate OTPs. Install it using pip:

pip install pyotp

Now, create a view in your app’s views.py to generate and send OTPs via email or Phone Number:

import pyotp
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.shortcuts import render, redirect

from .models import OTP

def send_otp(request):
    if request.method == 'POST':
        email = request.POST.get('email')
        user = User.objects.filter(email=email).first()
        
        if user:
            # Generate OTP
            otp_secret = pyotp.random_base32()
            otp = pyotp.TOTP(otp_secret)
            otp_code = otp.now()

            # Save OTP to the database
            otp_obj, created = OTP.objects.get_or_create(user=user, email=email)
            otp_obj.otp_secret = otp_secret
            otp_obj.save()

            # Send OTP via email
            subject = 'Your OTP for Login'
            message = f'Your OTP for login is: {otp_code}'
            from_email = '[email protected]'  # Update with your email
            recipient_list = [email]

            # Add Phone OTP API

            send_mail(subject, message, from_email, recipient_list)

            return redirect('verify_otp')
        else:
            return render(request, 'send_otp.html', {'message': 'Email not found'})
    else:
        return render(request, 'send_otp.html')

Step 4: OTP Verification

Create a view to verify OTPs during the login process:

import pyotp
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect

from .models import OTP

def verify_otp(request):
    if request.method == 'POST':
        email = request.POST.get('email')
        otp_code = request.POST.get('otp')
        
        otp_obj = OTP.objects.filter(email=email).first()
        
        if otp_obj:
            otp = pyotp.TOTP(otp_obj.otp_secret)
            print(otp_obj.otp_secret)
            print(otp_code)
            print(otp.verify(otp_code))
            if otp.verify(otp_code):
                otp_obj.is_verified = True
                otp_obj.save()
                user = authenticate(request, username=otp_obj.user.username, password='')
                if user:
                    login(request, user)
                    return redirect('home')  # Replace 'home' with the URL name of your home page
                else:
                    return redirect('login')  # Replace 'login' with the URL name of your home page
            else:
                return render(request, 'verify_otp.html', {'message': 'Invalid OTP'})
        else:
            return render(request, 'verify_otp.html', {'message': 'OTP not found'})
    else:
        return render(request, 'verify_otp.html')

Step 5: Update URLs

In your app’s urls.py, define the URLs for sending OTP and OTP verification views:

from django.urls import path
from . import views

urlpatterns = [
    path('send_otp/', views.send_otp, name='send_otp'),
    path('verify_otp/', views.verify_otp, name='verify_otp'),
    # Add other URLs as needed
]

Step 6: Update Templates

Create HTML templates for sending OTP and OTP verification. You can customize these templates according to your project’s design.

base.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Your Website Title{% endblock %}</title>
    <!-- Add your CSS and JavaScript links here -->
</head>

<body>
    <header>
        {% if message %}
            {{message}}
        {% endif %}
        {% if user.is_authenticated %}
        <p>Hello, {{ user.username }}! | <a href="{% url 'change_password' %}">Change Password</a> | <a href="{% url 'logout' %}">Logout</a></p>
        {% else %}
        <p><a href="{% url 'login' %}">Login</a> | <a href="{% url 'signup' %}">Sign up</a> | <a href="{% url 'password_reset' %}">Reset Password</a></p>
        <p><a href="{% url 'send_otp' %}">Login with OTP</a></p>
        {% endif %}
    </header>

    <main>
        {% block content %}
        {% endblock %}
    </main>

    <footer>
        <!-- Add your footer content here -->
    </footer>
</body>

</html>

send_otp.html

{% extends "base.html" %}

{% block title %}Send OTP{% endblock %}

{% block content %}
  <h2>Send OTP</h2>
  <form method="post" id="otp-form">
    {% csrf_token %}
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
    <button type="submit">Send OTP</button>
  </form>
{% endblock %}

{% block scripts %}
<script>
  // JavaScript to handle form submission and response
  document.getElementById('otp-form').addEventListener('submit', function (e) {
    e.preventDefault();

    const email = document.getElementById('email').value;
    const formData = new FormData();
    formData.append('email', email);

    fetch('{% url "send_otp" %}', {
      method: 'POST',
      body: formData,
    })
      .then(response => response.json())
      .then(data => {
        alert(data.message);
      })
      .catch(error => {
        console.error('Error:', error);
        alert('An error occurred.');
      });
  });
</script>
{% endblock %}

verify_otp.html

{% extends "base.html" %}

{% block title %}Verify OTP{% endblock %}

{% block content %}
  <h2>Verify OTP Sent Successful</h2>
  <form method="post" id="otp-verify-form">
    {% csrf_token %}
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required><br>
    <label for="otp">OTP:</label>
    <input type="text" id="otp" name="otp" required>
    <button type="submit">Verify OTP</button>
  </form>
{% endblock %}

{% block scripts %}
<script>
  // JavaScript to handle form submission and response
  document.getElementById('otp-verify-form').addEventListener('submit', function (e) {
    e.preventDefault();

    const email = document.getElementById('email').value;
    const otp = document.getElementById('otp').value;
    const formData = new FormData();
    formData.append('email', email);
    formData.append('otp', otp);

    fetch('{% url "verify_otp" %}', {
      method: 'POST',
      body: formData,
    })
      .then(response => response.json())
      .then(data => {
        alert(data.message);
        if (data.message === 'Login successful') {
          // Redirect to the desired page upon successful login
          window.location.href = '{% url "home" %}';  // Replace with your home URL
        }
      })
      .catch(error => {
        console.error('Error:', error);
        alert('An error occurred.');
      });
  });
</script>
{% endblock %}

Step 7: Update Base Template

In your base.html template, add a link to the send OTP page if the user is not authenticated. You can include this link in the navigation or wherever it fits your project’s design.

{% if user.is_authenticated %}
  <!-- Add authenticated user content here -->
{% else %}
  <p><a href="{% url 'send_otp' %}">Login with OTP</a></p>
{% endif %}

Step 8: Update urls.py

In your project’s urls.py, include the URLs of the OTP app:

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('appname.urls')),  # Replace 'appname' with your app's name
    path('otp/', include('otp_auth.urls')),  # Include OTP app URLs
]

Step 9: Configure Email Settings

Make sure your Django project is configured to send emails. Update your project’s settings (settings.py) with the SMTP email configuration:

# settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'your-smtp-server.com'  # Replace with your SMTP server
EMAIL_PORT = 587  # Use the appropriate port for your SMTP server
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[email protected]'  # Replace with your email
EMAIL_HOST_PASSWORD = 'your-email-password'  # Replace with your email password

Replace [email protected] and your_email_password with your Gmail email and password.

Please watch bellow video, to send email using Gmail.

Note that using your actual email and password directly in your code is not recommended for production. Instead, consider using environment variables or other secure methods to store these credentials. Read more on How to Protect Sensitive Data in Python Projects like Django and Flask

Step 10: Run the Application

Finally, start your Django development server:

python manage.py runserver

Now, you should have OTP-based login functionality via email in your Django application. Users can enter their email to receive an OTP, which they can use to verify and log in. Adjust the templates and styling to match your project’s design and branding.

Find this project on Github.

Blogs You Might Like to Read!