Skip to content

Chapter 9: User Authentication System

9.2 User Registration and Login

Registration Form

Creating user registration functionality requires custom forms to handle user input and validation:

python
# forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError

class CustomUserCreationForm(UserCreationForm):
    email = forms.EmailField(required=True)
    first_name = forms.CharField(max_length=30, required=True)
    last_name = forms.CharField(max_length=30, required=True)
    
    class Meta:
        model = User
        fields = ("username", "first_name", "last_name", "email", "password1", "password2")
    
    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise ValidationError("This email has already been registered")
        return email
    
    def save(self, commit=True):
        user = super().save(commit=False)
        user.email = self.cleaned_data["email"]
        user.first_name = self.cleaned_data["first_name"]
        user.last_name = self.cleaned_data["last_name"]
        if commit:
            user.save()
        return user

# More complex registration form
class ExtendedUserCreationForm(UserCreationForm):
    email = forms.EmailField(
        required=True,
        widget=forms.EmailInput(attrs={'class': 'form-control'})
    )
    first_name = forms.CharField(
        max_length=30,
        required=True,
        widget=forms.TextInput(attrs={'class': 'form-control'})
    )
    last_name = forms.CharField(
        max_length=30,
        required=True,
        widget=forms.TextInput(attrs={'class': 'form-control'})
    )
    
    class Meta:
        model = User
        fields = ("username", "first_name", "last_name", "email", "password1", "password2")
        widgets = {
            'username': forms.TextInput(attrs={'class': 'form-control'}),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Add styles to password fields
        self.fields['password1'].widget.attrs.update({'class': 'form-control'})
        self.fields['password2'].widget.attrs.update({'class': 'form-control'})
    
    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise ValidationError("This email has already been registered. Please use another email")
        return email
    
    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(username=username).exists():
            raise ValidationError("This username is already taken. Please choose another username")
        return username

Login/Logout Views

Implement custom login and logout functionality:

python
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import CustomUserCreationForm

def register(request):
    if request.method == 'POST':
        form = CustomUserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            username = form.cleaned_data.get('username')
            messages.success(request, f'Account {username} created successfully! You can now log in.')
            return redirect('login')
    else:
        form = CustomUserCreationForm()
    
    return render(request, 'registration/register.html', {'form': form})

def user_login(request):
    if request.method == 'POST':
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(username=username, password=password)
            if user is not None:
                login(request, user)
                messages.info(request, f"Welcome, {username}!")
                # Redirect to the page the user was previously visiting or home page
                next_page = request.GET.get('next', 'home')
                return redirect(next_page)
            else:
                messages.error(request, "Invalid username or password")
        else:
            messages.error(request, "Invalid username or password")
    else:
        form = AuthenticationForm()
    
    return render(request, 'registration/login.html', {'form': form})

@login_required
def user_logout(request):
    logout(request)
    messages.info(request, "You have successfully logged out")
    return redirect('home')

# Using decorator to check if user is logged in
@login_required
def profile(request):
    return render(request, 'registration/profile.html')

# Restrict access to pages for unauthenticated users only
from django.contrib.auth.decorators import user_passes_test

def anonymous_required(function=None, redirect_url=None):
    if not redirect_url:
        redirect_url = 'home'
    
    actual_decorator = user_passes_test(
        lambda u: u.is_anonymous,
        login_url=redirect_url
    )
    
    if function:
        return actual_decorator(function)
    return actual_decorator

@anonymous_required
def register_view(request):
    # Only unauthenticated users can access the registration page
    pass

Login template example:

html
<!-- registration/login.html -->
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h3>User Login</h3>
                </div>
                <div class="card-body">
                    {% if messages %}
                        {% for message in messages %}
                            <div class="alert alert-{{ message.tags }}">{{ message }}</div>
                        {% endfor %}
                    {% endif %}
                    
                    <form method="post">
                        {% csrf_token %}
                        
                        <div class="mb-3">
                            <label for="{{ form.username.id_for_label }}" class="form-label">Username</label>
                            {{ form.username }}
                            {% if form.username.errors %}
                                <div class="text-danger">{{ form.username.errors }}</div>
                            {% endif %}
                        </div>
                        
                        <div class="mb-3">
                            <label for="{{ form.password.id_for_label }}" class="form-label">Password</label>
                            {{ form.password }}
                            {% if form.password.errors %}
                                <div class="text-danger">{{ form.password.errors }}</div>
                            {% endif %}
                        </div>
                        
                        <button type="submit" class="btn btn-primary">Login</button>
                        <a href="{% url 'password_reset' %}" class="btn btn-link">Forgot password?</a>
                    </form>
                    
                    <div class="mt-3">
                        <p>Don't have an account? <a href="{% url 'register' %}">Register now</a></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Registration template example:

html
<!-- registration/register.html -->
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h3>User Registration</h3>
                </div>
                <div class="card-body">
                    {% if messages %}
                        {% for message in messages %}
                            <div class="alert alert-{{ message.tags }}">{{ message }}</div>
                        {% endfor %}
                    {% endif %}
                    
                    <form method="post">
                        {% csrf_token %}
                        
                        <div class="mb-3">
                            {{ form.username.label_tag }}
                            {{ form.username }}
                            {% if form.username.errors %}
                                <div class="text-danger">{{ form.username.errors }}</div>
                            {% endif %}
                            <div class="form-text">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</div>
                        </div>
                        
                        <div class="row">
                            <div class="col-md-6 mb-3">
                                {{ form.first_name.label_tag }}
                                {{ form.first_name }}
                                {% if form.first_name.errors %}
                                    <div class="text-danger">{{ form.first_name.errors }}</div>
                                {% endif %}
                            </div>
                            <div class="col-md-6 mb-3">
                                {{ form.last_name.label_tag }}
                                {{ form.last_name }}
                                {% if form.last_name.errors %}
                                    <div class="text-danger">{{ form.last_name.errors }}</div>
                                {% endif %}
                            </div>
                        </div>
                        
                        <div class="mb-3">
                            {{ form.email.label_tag }}
                            {{ form.email }}
                            {% if form.email.errors %}
                                <div class="text-danger">{{ form.email.errors }}</div>
                            {% endif %}
                        </div>
                        
                        <div class="mb-3">
                            {{ form.password1.label_tag }}
                            {{ form.password1 }}
                            {% if form.password1.errors %}
                                <div class="text-danger">{{ form.password1.errors }}</div>
                            {% endif %}
                            <div class="form-text">
                                <ul>
                                    <li>Your password can't be too similar to your other personal information.</li>
                                    <li>Your password must contain at least 8 characters.</li>
                                    <li>Your password can't be a commonly used password.</li>
                                    <li>Your password can't be entirely numeric.</li>
                                </ul>
                            </div>
                        </div>
                        
                        <div class="mb-3">
                            {{ form.password2.label_tag }}
                            {{ form.password2 }}
                            {% if form.password2.errors %}
                                <div class="text-danger">{{ form.password2.errors }}</div>
                            {% endif %}
                            <div class="form-text">For verification, please enter the same password as above.</div>
                        </div>
                        
                        <button type="submit" class="btn btn-primary">Register</button>
                    </form>
                    
                    <div class="mt-3">
                        <p>Already have an account? <a href="{% url 'login' %}">Login now</a></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Password Reset

Django provides complete password reset functionality:

python
# Custom password reset form
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.tokens import default_token_generator
from django.template import loader
from django.core.mail import EmailMultiAlternatives
from django.contrib.sites.shortcuts import get_current_site

class CustomPasswordResetForm(PasswordResetForm):
    def send_mail(self, subject_template_name, email_template_name,
                  context, from_email, to_email, html_email_template_name=None):
        """
        Send password reset email
        """
        subject = loader.render_to_string(subject_template_name, context)
        subject = ''.join(subject.splitlines())
        body = loader.render_to_string(email_template_name, context)
        
        email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
        if html_email_template_name is not None:
            html_email = loader.render_to_string(html_email_template_name, context)
            email_message.attach_alternative(html_email, 'text/html')
        
        email_message.send()

# Custom password reset view
from django.contrib.auth.views import PasswordResetView
from .forms import CustomPasswordResetForm

class CustomPasswordResetView(PasswordResetView):
    form_class = CustomPasswordResetForm
    template_name = 'registration/password_reset_form.html'
    email_template_name = 'registration/password_reset_email.html'
    subject_template_name = 'registration/password_reset_subject.txt'
    success_url = '/accounts/password_reset/done/'

# URL configuration
urlpatterns = [
    path('password_reset/', CustomPasswordResetView.as_view(), name='password_reset'),
    # Other authentication URLs
]

Password reset templates:

html
<!-- registration/password_reset_form.html -->
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header">
                    <h3>Reset Password</h3>
                </div>
                <div class="card-body">
                    <p>Please enter your email address. We will send a password reset link to your email.</p>
                    
                    <form method="post">
                        {% csrf_token %}
                        <div class="mb-3">
                            {{ form.email.label_tag }}
                            {{ form.email }}
                            {% if form.email.errors %}
                                <div class="text-danger">{{ form.email.errors }}</div>
                            {% endif %}
                        </div>
                        <button type="submit" class="btn btn-primary">Send Reset Link</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- registration/password_reset_email.html -->
{% autoescape off %}
Hello,

You received this email because you requested a password reset for your user account on {{ site_name }}.

Please click the link below and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}

Your username is: {{ user.get_username }}

If you didn't request a password reset, please ignore this email.

Thank you for using our website!
The {{ site_name }} Team
{% endautoescape %}

<!-- registration/password_reset_subject.txt -->
{{ site_name }} Password Reset

Email Verification

Implement email verification functionality:

python
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils.crypto import get_random_string

class EmailVerification(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    token = models.CharField(max_length=50, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    is_verified = models.BooleanField(default=False)
    
    def save(self, *args, **kwargs):
        if not self.token:
            self.token = get_random_string(50)
        super().save(*args, **kwargs)

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

class EmailVerificationForm(forms.Form):
    email = forms.EmailField()
    
    def clean_email(self):
        email = self.cleaned_data['email']
        if not User.objects.filter(email=email, is_active=True).exists():
            raise forms.ValidationError("This email address is not registered or not activated")
        return email

# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.contrib.sites.shortcuts import get_current_site
from .models import EmailVerification

def send_verification_email(request, user):
    """Send verification email"""
    # Create or get verification record
    verification, created = EmailVerification.objects.get_or_create(user=user)
    verification.token = get_random_string(50)
    verification.save()
    
    # Build verification link
    current_site = get_current_site(request)
    subject = 'Please verify your email address'
    message = render_to_string('registration/verification_email.html', {
        'user': user,
        'domain': current_site.domain,
        'token': verification.token,
    })
    
    send_mail(subject, message, 'noreply@yoursite.com', [user.email])

def verify_email(request, token):
    """Verify email"""
    try:
        verification = EmailVerification.objects.get(token=token)
        if not verification.is_verified:
            verification.is_verified = True
            verification.save()
            messages.success(request, 'Your email has been successfully verified!')
        else:
            messages.info(request, 'Your email has already been verified.')
    except EmailVerification.DoesNotExist:
        messages.error(request, 'Invalid verification link.')
    
    return redirect('home')

# Send verification email after user registration
def register(request):
    if request.method == 'POST':
        form = CustomUserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            # Send verification email
            send_verification_email(request, user)
            messages.success(request, 'Registration successful! Please check your email and click the verification link.')
            return redirect('login')
    else:
        form = CustomUserCreationForm()
    
    return render(request, 'registration/register.html', {'form': form})

Email verification template:

html
<!-- registration/verification_email.html -->
{% autoescape off %}
Hello {{ user.username }},

Thank you for registering on {{ site_name }}!

Please click the link below to verify your email address:
http://{{ domain }}{% url 'verify_email' token=token %}

If you didn't register for an account, please ignore this email.

Thank you!
The {{ site_name }} Team
{% endautoescape %}

With these features, you can build a complete user authentication system, including registration, login, password reset, and email verification functions.

Summary

User registration and login are core features of web applications:

  1. ✅ Custom registration forms handle user input and validation
  2. ✅ Login/logout views manage user sessions
  3. ✅ Password reset functionality ensures account security
  4. ✅ Email verification ensures account validity
  5. ✅ Good integration of templates and messaging systems

A complete authentication process improves user experience and system security.

Next

We will learn about advanced usage of permissions and decorators.

9.3 Role Permissions →

Table of Contents

Return to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).