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:
- Django installed (as mentioned in the original blog).
- 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.
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.