Customizing Django Authentication Login Pages with Form Validation and CAPTCHA

Configure URL Routing for Authentication Views

# apps/urls.py
from django.urls import path
from apps.views.account import login_view, logout_view, generate_captcha

urlpatterns = [
    path('login/', login_view, name='login'),
    path('logout/', logout_view, name='logout'),
    path('captcha/', generate_captcha, name='captcha'),
]

Import Reuqired Modules in View Module

Create apps/views/account.py and include:

import io
from django.shortcuts import render, redirect, HttpResponse
from django import forms
from django.core.exceptions import ValidationError

from apps import models
from apps.utils.security import hash_password
from apps.utils.captcha import generate_verification_image

Define a Customm Login Form with Field Styling and Hashing

class AuthenticationForm(forms.Form):
    username = forms.CharField(
        label="Username",
        max_length=32,
        widget=forms.TextInput(),
        required=True
    )
    secret = forms.CharField(
        label="Password",
        widget=forms.PasswordInput(render_value=True),
        required=True
    )
    challenge = forms.CharField(
        label="CAPTCHA",
        widget=forms.TextInput(),
        required=True
    )

    def clean_secret(self):
        raw = self.cleaned_data.get("secret")
        return hash_password(raw)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            if not field.widget.attrs:
                field.widget.attrs = {}
            field.widget.attrs.update({
                'class': 'form-control',
                'placeholder': field.label
            })

Implemant the Login Handler with Session Management and Validation

def login_view(request):
    if request.method == 'GET':
        return render(request, 'auth/login.html', {'form': AuthenticationForm()})

    bound_form = AuthenticationForm(data=request.POST)
    if not bound_form.is_valid():
        return render(request, 'auth/login.html', {'form': bound_form})

    user_input = bound_form.cleaned_data.copy()
    submitted_captcha = user_input.pop('challenge')
    expected_captcha = request.session.get('captcha_value')

    if submitted_captcha.upper() != expected_captcha:
        bound_form.add_error('challenge', 'Invalid CAPTCHA')
        return render(request, 'auth/login.html', {'form': bound_form})

    # Extend session lifetime to 7 days
    request.session.set_expiry(60 * 60 * 24 * 7)

    authenticated_user = models.Admin.objects.filter(**user_input).first()
    if authenticated_user:
        request.session['user_profile'] = {
            'id': authenticated_user.id,
            'display_name': authenticated_user.name
        }
        return redirect('admin_dashboard')
    else:
        bound_form.add_error('secret', ValidationError('Credentials do not match'))
        return render(request, 'auth/login.html', {'form': bound_form})

    return render(request, 'auth/login.html', {'form': bound_form})

Build the Logout Endpoint

def logout_view(request):
    request.session.flush()
    return redirect('login')

Serve Dynamic CAPTCHA Images

def generate_captcha(request):
    image, text = generate_verification_image()
    request.session['captcha_value'] = text
    request.session.set_expiry(60)  # 1-minute expiry

    buffer = io.BytesIO()
    image.save(buffer, format='PNG')
    return HttpResponse(buffer.getvalue(), content_type='image/png')

Render the Login Template with Bootstrap Styling

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sign In</title>
    <link rel="stylesheet" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}">
    <style>
        .auth-card { width: 420px; margin: 150px auto; }
    </style>
</head>
<body>
<div class="container auth-card">
    <div class="panel panel-default">
        <div class="panel-heading text-center"><h3>Account Access</h3></div>
        <div class="panel-body">
            <form method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label>{{ form.username.label }}</label>
                    {{ form.username }}
                    <span class="text-danger">{{ form.username.errors.0 }}</span>
                </div>
                <div class="form-group">
                    <label>{{ form.secret.label }}</label>
                    {{ form.secret }}
                    <span class="text-danger">{{ form.secret.errors.0 }}</span>
                </div>
                <div class="form-group">
                    <label>{{ form.challenge.label }}</label>
                    <div class="row">
                        <div class="col-sm-7">{{ form.challenge }}</div>
                        <div class="col-sm-5">
                            <img src="{% url 'captcha' %}" alt="CAPTCHA" id="captcha-img" />
                        </div>
                    </div>
                    <span class="text-danger">{{ form.challenge.errors.0 }}</span>
                </div>
                <button type="submit" class="btn btn-success btn-block">Log In</button>
            </form>
        </div>
    </div>
</div>
<script>
    document.getElementById('captcha-img').onclick = function () {
        this.src = '{% url "captcha" %}?' + Math.random();
    };
</script>
</body>
</html>

Tags: Django Authentication Form Validation CAPTCHA Session Management

Posted on Wed, 13 May 2026 20:09:56 +0000 by blockage