Django Authentication Tutorial

December 06, 2025
30 min read
Lalit Mahato
Django Authentication Tutorial

Prerequisites

Before starting, ensure you have the following ready:

  • Basic Knowledge: A fundamental understanding of Python and Django project structure.

  • Environment: A Django project created and running.

  • Dependencies: We need the Pillow library to handle profile image uploads.

pip install Pillow

Phase 1: Setup & Custom User Model

Step 1: Create the App

Run this in your terminal:

python manage.py startapp users

Connect to Main Project

To make all these URLs work, we must connect the app to the main project. Open the urls.py folder in your main project folder (where settings.py is).

# todo_list/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('users.urls')), # Connects your new app
]

# This allows images to be served during development
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Step 2: Create the Custom Model

Open users/models.py. We inherit from AbstractUser keeping Django's security features while adding your specific fields.

# users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    GENDER_CHOICES = (
        ('M', 'Male'),
        ('F', 'Female'),
        ('O', 'Other'),
    )
    
    date_of_birth = models.DateField(null=True, blank=True)
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True)
    photo = models.ImageField(upload_to='profile_photos/', blank=True, null=True)

    def __str__(self):
        return self.username

Step 3: Register in Admin

Open users/admin.py. This ensures you can see these new fields in the Django Admin panel.

# users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser

class CustomUserAdmin(UserAdmin):
    model = CustomUser
    # Add the new fields to the admin interface
    fieldsets = UserAdmin.fieldsets + (
        ('Personal Info', {'fields': ('date_of_birth', 'gender', 'photo')}),
    )

admin.site.register(CustomUser, CustomUserAdmin)

Step 4: Configure Settings

Open your project's settings.py. Add these lines to tell Django to use your model and where to save images.

# settings.py

# 1. Point to your new model
AUTH_USER_MODEL = 'users.CustomUser'

# 2. Configure media (image) handling
import os
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Step 5: Apply Migrations

Now create the database tables.

python manage.py makemigrations
python manage.py migrate

Phase 2: Base Template (Bootstrap)

We need a main layout file to avoid repeating code. We already created the base.html template in our previous classes, so we are going to use it.


Phase 3: The Signup Feature

We will now build the Signup logic, then the template, then the URL.

Step 1: Create the Form

We need a form that handles the new fields. Create a file users/forms.py.

# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ['username', 'email', 'date_of_birth', 'gender', 'photo']
        
        widgets = {
            'date_of_birth': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
            'gender': forms.Select(attrs={'class': 'form-select'}),
            'photo': forms.FileInput(attrs={'class': 'form-control'}),
        }

    # Loop through fields to add Bootstrap styling
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            if 'class' not in self.fields[field].widget.attrs:
                self.fields[field].widget.attrs.update({'class': 'form-control'})

Step 2: The Signup View

Open users/views.py.

# users/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import CustomUserCreationForm

def signup_view(request):
    if request.method == 'POST':
        # request.FILES is required for image upload
        form = CustomUserCreationForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            messages.success(request, 'Account created successfully! Please login.')
            return redirect('login') # We will create this URL next
    else:
        form = CustomUserCreationForm()
    
    return render(request, 'users/signup.html', {'form': form})

Step 3: The Signup Template

Create users/templates/users/signup.html.

{% extends 'base.html' %}

{% block content %}
<div class="card mx-auto" style="max-width: 500px;">
    <div class="card-header bg-primary text-white">Sign Up</div>
    <div class="card-body">
        <form method="POST" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit" class="btn btn-primary w-100">Register</button>
        </form>
        <p class="mt-2 text-center">Already have an account? <a href="{% url 'login' %}">Login</a></p>
    </div>
</div>
{% endblock %}

Step 4: The Signup URL

Create users/urls.py and add the path.

# users/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('signup/', views.signup_view, name='signup'),
]

Phase 4: The Login Feature

Step 1: The Login View

Open users/views.py and add this function.

# users/views.py (Append this)
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm

def login_view(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}!")
                return redirect('task_list') # Redirect to dashboard/home in real app
            else:
                messages.error(request, "Invalid username or password.")
        else:
            messages.error(request, "Invalid username or password.")
    else:
        form = AuthenticationForm()
        # Add Bootstrap styling manually for built-in form
        for field in form.fields.values():
            field.widget.attrs['class'] = 'form-control'

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

Step 2: The Login Template

Create users/templates/users/login.html.

{% extends 'base.html' %}

{% block content %}
<div class="card mx-auto" style="max-width: 400px;">
    <div class="card-header bg-success text-white">Login</div>
    <div class="card-body">
        <form method="POST">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit" class="btn btn-success w-100">Login</button>
        </form>
        <p class="mt-2 text-center">No account? <a href="{% url 'signup' %}">Sign up</a></p>
    </div>
</div>
{% endblock %}

Step 3: Update URLs

Open users/urls.py and add the login path.

# users/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('signup/', views.signup_view, name='signup'),
    path('login/', views.login_view, name='login'), # Added this
]

Phase 5: The Logout Feature

Step 1: The Logout View

Open users/views.py and add this function.

# users/views.py (Append this)
from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    messages.info(request, "You have been logged out.")
    return redirect('login')

Step 2: Update URLs

Open users/urls.py and add the logout path.

# users/urls.py
urlpatterns = [
    path('signup/', views.signup_view, name='signup'),
    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'), # Added this
]

Phase 6: The Change Password Feature

Step 1: The Change Password View

Open users/views.py and add this function.

# users/views.py (Append this)
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.decorators import login_required

@login_required
def change_password_view(request):
    if request.method == 'POST':
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            # This is critical: keeps user logged in after password change
            update_session_auth_hash(request, user)
            messages.success(request, 'Your password was successfully updated!')
            return redirect('login') # Or redirect to profile
        else:
            messages.error(request, 'Please correct the error below.')
    else:
        form = PasswordChangeForm(request.user)
        for field in form.fields.values():
            field.widget.attrs['class'] = 'form-control'

    return render(request, 'users/change_password.html', {'form': form})

Step 2: The Change Password Template

Create users/templates/users/change_password.html.

{% extends 'base.html' %}

{% block content %}
<div class="card mx-auto" style="max-width: 500px;">
    <div class="card-header bg-warning">Change Password</div>
    <div class="card-body">
        <form method="POST">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit" class="btn btn-warning w-100">Update Password</button>
        </form>
    </div>
</div>
{% endblock %}

Step 3: Update URLs

Open users/urls.py and add the final path.

# users/urls.py
urlpatterns = [
    path('signup/', views.signup_view, name='signup'),
    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'),
    path('change-password/', views.change_password_view, name='change_password'), # Added this
]

Run the server test:

python manage.py runserver

 

Lalit Mahato

Lalit Mahato

Software Engineer | Machine Learning Enthusiast

Innovative and results-driven software developer with 5+ years of experience in designing, developing, and deploying high-quality software solutions. Proficient in various programming languages and technologies, with a keen interest in …

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment

Search

Categories

Related Posts

Getting Started With Django
Getting Started With Django

Nov 20, 2025

Read More
Django CRUD Operations: Step-by-Step Guide
Django CRUD Operations: Step-by-Step Guide

Nov 19, 2025

Read More