Mastering User Authentication in Django: A Step-by-Step Code Guide

Intermediate 30 minutes Dec. 25, 2025

User authentication is a critical aspect of any web application. Django makes this incredibly powerful yet easy to implement by providing a robust foundation for storing customer data without needing to create custom models from scratch.

In this tutorial, we will walk through the complete workflow, from the built-in user model to custom forms and restricted page access.

Learn to implement a full authentication system in Django. Cover built-in user models, custom forms, secure login flows, and page restrictions today.

Step 1: The Foundation: Django’s Built-in User Model

Before writing code, it is essential to understand where user data lives. We do not need to create a customer database from scratch. When you initialize a project and run migrations, Django automatically creates a table called auth_user.

This built-in model is a robust foundation that includes essential fields such as:

  • Username and Email
  • Securely Hashed Passwords

Accessing User Data

Django makes accessing user data seamless in both your logic and your design:

  • In Views: You can access the logged-in user via the request.user object. To verify their status, you can use the check request.user.is_authenticated.
  • In Templates: The user object is globally available. You can display a name using {{ user.username }} or use conditional blocks like {% if user.is_authenticated %} to toggle content visibility.

You can access this user data directly in your views and templates:

In your Python Views (views.py):

python
# Access the currently logged-in user object
current_user = request.user

# Check if the user is logged in
if request.user.is_authenticated:
    # Logic for logged-in users
    pass

In your HTML Templates:

html
<p>Welcome, {{ user.username }}</p>

{% if user.is_authenticated %}
    <p>You are logged in.</p>
{% endif %}

Step 2: Creating the Sign-Up View

We need a user-facing interface for registration. We will start by importing the UserCreationForm and creating a view to handle both rendering the form and saving the data.

In views.py:

python
#products/views.py

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages

def signup_view(request):
    # form = UserCreationForm() #Buildin Form
    form = CustomUserCreationForm()  # Custom form

    # Handle POST request (Form Submission)
    if request.method == "POST":
        # form = UserCreationForm(request.POST) #Buildin Form
        form = CustomUserCreationForm(request.POST)  # Custom form
        if form.is_valid():
            form.save()
            # form = UserCreationForm() #Buildin Form
            form = CustomUserCreationForm()  # Custom form
            messages.success(request, "Registration Successful! you can now log in.")

    # Handle GET request (Display empty form)
    return render(request, "authss/signup.html", {"form": form})

Step 3: Setting up the URL

To make the view accessible, map it to a specific URL path.

In urls.py:

python
#products/urls.py

from django.urls import path
from . import views

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

Step 4: Building the Sign-Up Template

We create a template inside an auth folder to keep things organized. Crucially, we must include the CSRF token for security.

In templates/authss/signup.html:

html
{% extends "base.html" %}
{% block title %} SignUp {% endblock %}

{% block content %}
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card shadow">
                <div class="card-body">
                    <h2>Signup Form</h2>
                    {% if messages %}
                    {% for message in messages %}
                    <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                        {{message}}
                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="alert"></button>
                    </div>
                    {% endfor %}
                    {% endif %}
                    <!-- <form method="post">
                        {% csrf_token %}
                        {{ form.as_p }}
                        <button class="btn btn-primary">Register</button>
                    </form> -->

                    <form method="post">
                        {% csrf_token %}
                        {% for field in form %}
                        <div class="mb-1">
                            <label class="form-label text-dark">{{ field.label }}</label>
                            {{ field }}
                            {% if field.help_text %}
                            <small class="form-text text-muted">
                                {{ field.help_text|safe }}
                            </small>
                            {% endif %}
                            {% if field.errors %}
                            <div class="text-danger small">
                                {{ field.errors }}
                            </div>
                            {% endif %}
                        </div>
                        {% endfor%}
                        <button class="btn btn-primary">Register</button>
                    </form>
                    <p class="mt-3 text-center">Already have an account? <a href="{% url 'login' %}"
                            class="text-decoration-none">Click here to login</a></p>
                </div>
            </div>
        </div>
    </div>

</div>
{% endblock %}

Step 5: Customizing the Forms

The default Django forms are functional but often lack style. To match a custom brand or Bootstrap theme, we need to customize the form using a forms.py file.

In forms.py:

python
#products/forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User

class CustomUserCreationForm(UserCreationForm):
    username = forms.CharField(
        widget=forms.TextInput(
            attrs={"class": "form-control", "placeholder": "Enter username"}
        ),
        label="User Name",
        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
    )
    password1 = forms.CharField(
        widget=forms.PasswordInput(
            attrs={"class": "form-control", "placeholder": "Enter password"}
        ),
        label="Password",
        help_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>      

        """,
    )
    password2 = forms.CharField(
        widget=forms.PasswordInput(
            attrs={"class": "form-control", "placeholder": "confirm password"}
        ),
        label="Password Confirmations",
        help_text="Enter the same password as before, for verification.",
    )

    class Meta:
        model = User
        fields = ["username", "password1", "password2"]

Note: You would then update your signup_view to import and use CustomUserCreationForm instead of the default UserCreationForm.

We create a CustomUserCreationForm class that inherits from the standard UserCreationForm. Inside this class, we can:

  • Add Widgets: Inject CSS classes (like form-control) directly into the HTML tags Django generates.
  • Add Placeholders: Include helpful text guides like "Enter Username".

Step 6: Login and Logout Logic

Implementing login and logout relies on Django's built-in authentication functions.

In views.py:

python
#products/views.py

from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm

def login_view(request):
    if request.method == 'POST':
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            # Log the user in
            user = form.get_user()
            login(request, user)
            return redirect('product_list')
    else:
        form = AuthenticationForm()
    return render(request, 'auth/login.html', {'form': form})

def logout_view(request):
    logout(request) # Clears the session
    return redirect('login')

To make the view accessible, map it to a specific URL path.

In urls.py:

python
#products/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # ... other patterns ... 
    path("login/", login_view, name="login"),
    path("logout/", logout_view, name="logout"),
]

In templates/authss/login.html:

html
#templates/authss/login.html

{% extends "base.html" %}
{% block title %} Login {% endblock %}

{% block content %}
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card shadow">
                <div class="card-body">
                    <h2>Login Form</h2>
                    {% if messages %}
                    {% for message in messages %}
                    <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                        {{message}}
                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="alert"></button>
                    </div>
                    {% endfor %}
                    {% endif %}
                    <!-- <form method="post">
                        {% csrf_token %}
                        {{ form.as_p }}
                        <button class="btn btn-primary">Register</button>
                    </form> -->

                    <form method="post">
                        {% csrf_token %}
                        {% for field in form %}
                        <div class="mb-1">
                            <label class="form-label text-dark">{{ field.label }}</label>
                            {{ field }}
                            {% if field.help_text %}
                            <small class="form-text text-muted">
                                {{ field.help_text|safe }}
                            </small>
                            {% endif %}
                            {% if field.errors %}
                            <div class="text-danger small">
                                {{ field.errors }}
                            </div>
                            {% endif %}
                        </div>
                        {% endfor%}
                        <button class="btn btn-primary">Login</button>
                    </form>
                    <p class="mt-3 text-start">If you have not account <a href="{% url 'signup' %}"
                            class="text-decoration-none">click here to signup</a></p>
                </div>
            </div>
        </div>
    </div>

</div>
{% endblock %}

In forms.py:

python
#products/forms.py

from django.contrib.auth.forms import AuthenticationForm
from django import forms
from django.contrib.auth.models import User

class CustomAuthenticationForm(AuthenticationForm):
    username = forms.CharField(
        widget=forms.TextInput(
            attrs={"class": "form-control", "placeholder": "Enter username"}
        ),
        label="User Name",
    )
    password = forms.CharField(
        widget=forms.PasswordInput(
            attrs={"class": "form-control", "placeholder": "Enter password"}
        ),
        label="Password",
    )
    remember_me = forms.BooleanField(
        required=False, initial=True, label="Remember Me", widget=forms.CheckboxInput()
    )
  • Add Features: Include a "Remember Me" checkbox by adding a simple Boolean field to the form.

Step 7: Dynamic Navigation Bar

We can update the navigation bar to show different links depending on whether the user is a guest or a member.

In navbar.html:

html
#templates/navbar.html

<nav class="navbar navbar-expand-lg bg-dark navbar-dark">
    <div class="container-fluid">
        <!-- other codes -->
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <!-- other codes -->
            <ul class="navbar-nav ms-auto">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
                        aria-expanded="false">
                        Account
                    </a>
                    <ul class="dropdown-menu dropdown-menu-end">
                        {% if user.is_authenticated %}
                        <li><a class="dropdown-item" href="#">Hi, {{ user.username }}</a></li>
                        <li>
                            <hr class="dropdown-divider">
                        </li>
                        <li><a class="dropdown-item" href="{% url 'logout' %}">Logout</a></li>
                        {% else %}
                        <li><a class="dropdown-item" href="#">Hi, Guest</a></li>
                        <li>
                            <hr class="dropdown-divider">
                        </li>
                        <li><a class="dropdown-item" href="{% url 'login' %}">Login</a></li>
                        <li><a class="dropdown-item" href="{% url 'signup' %}">Signup</a></li>
                        {% endif %}
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>

Step 8: Restricting Page Access

Finally, we ensure that sensitive pages (like a product list) are only accessible to authenticated users by using decorators.

In views.py:

python
#products/views.py

from django.contrib.auth.decorators import login_required

# Decorator redirects unauthenticated users to the login page
@login_required(login_url='login') 
def product_list(request):
    # Other Codes
    return render(request, 'products/product_list.html')

With these steps, you have a fully functional authentication system with secure data storage, custom forms, and protected routes. In the next guide, we will look at building a Shopping Cart system using Django Sessions!

Conclusion

And there you have it! We have successfully built a complete, secure, and professional authentication system. We now have a secure database for user accounts, custom-styled forms, a dynamic navigation bar that reacts to the user's status, and protected content that requires a login.

In the next tutorial, we will explore the Shopping Cart system, where we will learn how to allow users to add products, manage quantities, and store that data using Django Sessions.

If you found this tutorial helpful, please like, share, and comment, and don't forget to subscribe to stay updated with all our Django content.

Do You Need Side Hustle?

Get Paid to Share Your Opinion: User Interviews Can Get You Quick Cash

Ever heard of User Interviews? It's a platform that pays you for your feedback on products and services. The best part is that you can get a bonus just for signing up and completing your first study. I've already earned money with them, and you can, too.

Click here to sign up and get your bonus! Please wait until Sign Up page load.

Disclaimer: Referral bonus amounts are subject to change. The bonus is awarded after you complete your first paid study and may take a few days to process. All terms and conditions are set by User Interviews, and it's best to check their official site for the most up-to-date program rules.


Earn Cash for Your Opinions and a Bonus to Boot: Try Respondent.io

Respondent is a top platform that pays you for taking part in market research and user interviews. I've used it to find great paid studies, and you can, too. Their referral program is a fantastic way to earn more: when a friend you refer signs up and completes a study, you both get a bonus. It’s a great way to earn a little extra for yourself while helping a friend find paid work.

Click here to sign up and get your bonus!

Disclaimer: Referral bonus amounts and terms are subject to change by Respondent.io. The bonus is typically awarded after the referred friend completes their first paid study. Please check their official referral program page for the most up-to-date details.


What’s Next?

You now have a secure database for accounts, custom-styled forms, and protected content. This sets the stage for advanced features. In the next post, we will explore the Shopping Cart system, discussing how to manage product quantities and store data using Django Sessions.

Learn More