Django Email Verification
Introduction
Email verification is an essential security feature for web applications. It ensures that users provide valid email addresses during registration and proves ownership of those emails. This verification step helps prevent spam accounts and provides an additional layer of security for your Django application.
In this tutorial, you'll learn how to implement email verification in Django from scratch. We'll create a system where new users receive a verification email with a unique token after registration. Users will need to click on the verification link to activate their accounts before they can log in.
Why Email Verification Matters
Before diving into the implementation, let's understand why email verification is important:
- Confirms identity: Ensures users provide real email addresses they own
- Reduces spam: Prevents automated bots from creating numerous fake accounts
- Enhances security: Adds an extra authentication factor
- Improves user data quality: Ensures valid contact information in your database
Prerequisites
To follow along with this tutorial, you should have:
- Basic knowledge of Django
- A Django project set up with the authentication system
- Python 3.6 or newer
- Understanding of how Django models and views work
Setting Up Email Backend
First, let's configure Django to send emails. For development purposes, we'll use the console backend, which prints emails to the console instead of actually sending them.
Add these settings to your settings.py
file:
# Email settings
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # For development only
EMAIL_HOST = 'smtp.gmail.com' # For production with Gmail
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[email protected]' # Your email address
EMAIL_HOST_PASSWORD = 'your-email-password' # Your email password/app password
For production, you should:
- Use a real email service (Gmail, SendGrid, AWS SES, etc.)
- Store sensitive credentials using environment variables
- Never commit email credentials to version control
Creating a User Profile Model with Verification
We'll need to extend the user model to include verification fields. Create a UserProfile
model in your models.py
:
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import uuid
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
email_verified = models.BooleanField(default=False)
verification_token = models.UUIDField(default=uuid.uuid4)
token_created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.user.username}'s Profile"
def token_is_valid(self):
# Token is valid for 24 hours after creation
return timezone.now() < self.token_created_at + timezone.timedelta(hours=24)
After creating this model, run migrations:
python manage.py makemigrations
python manage.py migrate
Registration and Email Sending
Now, let's create a registration view that will create a user, generate a verification token, and send an email. First, create a form in forms.py
:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Next, let's create the registration view in views.py
:
from django.shortcuts import render, redirect
from django.contrib import messages
from django.core.mail import send_mail
from django.conf import settings
from django.template.loader import render_to_string
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()
# Create user profile
profile = UserProfile.objects.create(user=user)
# Generate verification URL
token = profile.verification_token
verification_url = request.build_absolute_uri(
f'/verify-email/{token}/'
)
# Send verification email
subject = 'Verify your account'
message = f'Please click the link to verify your account: {verification_url}'
html_message = render_to_string('email_verification.html', {
'user': user,
'verification_url': verification_url,
})
send_mail(
subject=subject,
message=message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=[user.email],
html_message=html_message,
fail_silently=False,
)
messages.success(request, 'Account created! Please check your email to verify your account.')
return redirect('login')
else:
form = UserRegistrationForm()
return render(request, 'register.html', {'form': form})
Creating Email Templates
Create an HTML email template by making a new file templates/email_verification.html
:
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h2>Verify Your Email Address</h2>
<p>Hi {{ user.username }},</p>
<p>Thank you for registering! Please click the button below to verify your email address:</p>
<p>
<a href="{{ verification_url }}" class="button">Verify Email</a>
</p>
<p>Or copy and paste this link in your browser:</p>
<p>{{ verification_url }}</p>
<p>This link will expire in 24 hours.</p>
<p>If you didn't register on our site, please ignore this email.</p>
</div>
</body>
</html>
Implementing Email Verification View
Now, let's create a view to handle the verification process when users click the link in their emails:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from django.utils import timezone
from .models import UserProfile
import uuid
def verify_email(request, token):
try:
# Convert string token to UUID
verification_token = uuid.UUID(token)
# Find the profile with this token
profile = get_object_or_404(UserProfile, verification_token=verification_token)
# Check if token is expired
if not profile.token_is_valid():
return render(request, 'verification_failed.html', {
'message': 'Verification link has expired. Please request a new one.'
})
# Check if already verified
if profile.email_verified:
return render(request, 'verification_success.html', {
'message': 'Your email was already verified. You can log in.'
})
# Verify the email
profile.email_verified = True
profile.save()
return render(request, 'verification_success.html', {
'message': 'Your email has been verified successfully! You can now log in.'
})
except (ValueError, uuid.BadUUIDError):
return render(request, 'verification_failed.html', {
'message': 'Invalid verification link.'
})
Create templates for verification success and failure:
templates/verification_success.html
:
{% extends "base.html" %}
{% block content %}
<div class="verification-container">
<h2>Email Verification</h2>
<div class="alert alert-success">
<p>{{ message }}</p>
</div>
<p><a href="{% url 'login' %}" class="btn btn-primary">Go to Login</a></p>
</div>
{% endblock %}
templates/verification_failed.html
:
{% extends "base.html" %}
{% block content %}
<div class="verification-container">
<h2>Email Verification</h2>
<div class="alert alert-danger">
<p>{{ message }}</p>
</div>
<p><a href="{% url 'register' %}" class="btn btn-primary">Register Again</a></p>
</div>
{% endblock %}
Updating URLs
Add the URL patterns in your urls.py
:
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register, name='register'),
path('verify-email/<str:token>/', views.verify_email, name='verify_email'),
# Add other auth URLs as needed
]
Enhancing Login to Check Verification Status
Finally, let's modify the login process to check if a user's email is verified:
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .models import UserProfile
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
# Check if email is verified
try:
profile = UserProfile.objects.get(user=user)
if not profile.email_verified:
messages.error(request, 'Please verify your email before logging in.')
return redirect('login')
login(request, user)
return redirect('dashboard')
except UserProfile.DoesNotExist:
# If profile doesn't exist, create one and require verification
profile = UserProfile.objects.create(user=user)
messages.error(request, 'Please verify your email before logging in.')
return redirect('login')
else:
messages.error(request, 'Invalid username or password.')
return render(request, 'login.html')
Resending Verification Emails (Optional)
Let's create a utility function to resend verification emails if users didn't receive them:
from django.utils import timezone
import uuid
def resend_verification_email(request):
if request.method == 'POST':
email = request.POST.get('email')
try:
user = User.objects.get(email=email)
profile = UserProfile.objects.get(user=user)
# Update verification token and timestamp
profile.verification_token = uuid.uuid4()
profile.token_created_at = timezone.now()
profile.save()
# Generate new verification URL
verification_url = request.build_absolute_uri(
f'/verify-email/{profile.verification_token}/'
)
# Send new verification email
subject = 'Verify your account'
message = f'Please click the link to verify your account: {verification_url}'
html_message = render_to_string('email_verification.html', {
'user': user,
'verification_url': verification_url,
})
send_mail(
subject=subject,
message=message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=[user.email],
html_message=html_message,
fail_silently=False,
)
messages.success(request, 'Verification email resent. Please check your inbox.')
except (User.DoesNotExist, UserProfile.DoesNotExist):
messages.error(request, 'No account found with that email address.')
return render(request, 'resend_verification.html')
Don't forget to create a template for this and add it to your URLs.
Testing the Implementation
To test your implementation:
- Register a new user account
- Check your console output (or email inbox in production) for the verification email
- Click the verification link or copy-paste it into your browser
- Try to log in before verification (should be denied)
- Verify the email and try logging in again (should succeed)
Real-world Considerations
When implementing email verification in a production environment:
- Security: Use HTTPS for all verification links
- Expiry time: Set reasonable token expiration times (24-48 hours is common)
- Rate limiting: Limit verification email resends to prevent abuse
- Email deliverability: Use a reliable email service like SendGrid, Mailgun, or AWS SES
- Error handling: Provide clear error messages if verification fails
- Logging: Log verification attempts for debugging and security monitoring
- Accessibility: Ensure emails render well on different devices and email clients
Complete Example Project Structure
Here's how your final project structure might look:
my_project/
├── accounts/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations/
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── my_project/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── templates/
│ ├── base.html
│ ├── email_verification.html
│ ├── login.html
│ ├── register.html
│ ├── resend_verification.html
│ ├── verification_failed.html
│ └── verification_success.html
└── manage.py
Summary
In this tutorial, you learned how to implement a complete email verification system for Django:
- Setting up the email backend configuration
- Creating a UserProfile model with verification fields
- Implementing the registration process that sends verification emails
- Building the verification view that validates tokens
- Enhancing the login process to check verification status
- Adding functionality to resend verification emails
Email verification is a crucial security feature for any modern web application. It ensures that users provide valid email addresses and helps protect your system against spam and automated registrations.
Additional Resources
- Django Email Documentation
- Django Authentication System
- SendGrid Email Service - A popular email service for production use
- Django-allauth - A comprehensive authentication package that includes email verification
Exercises
- Enhance the system to generate a new token when resending verification emails
- Add a page where users can request verification email resends
- Implement a system to notify admins when multiple verification attempts fail for the same user
- Create an admin interface to manually verify users
- Add email templates for different scenarios (welcome email, password reset, etc.)
Happy coding!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)