DEV Community

Cover image for How to Build a Custom Django Authentication System: A Comprehensive Guide.
Doyin Elugbadebo
Doyin Elugbadebo

Posted on

How to Build a Custom Django Authentication System: A Comprehensive Guide.

Introduction

An authentication system is a critical component of any modern application. In other to ensure secure access, protect sensitive data, and provide personalized user experiences, then implementing a robust authentication mechanism is essential.

In this article, I will guide you through building a Django Login and Registration system using a custom user model. Whether you're starting from scratch or enhancing an existing application, this tutorial will provide you with the insights needed to build a fully functional Django authentication system ready to integrate seamlessly into your next project.

Check out a live demo of the app, which showcases functionality similar to what you'll be building

Download the complete code from the GitHub repository

Let’s get started!

Table of Contents

  • Understanding Authentication and Authorization
  • Setting Up Your Environment
  • Creating an Index Page
  • Integrating the View into Django's MVC Architecture.
  • Creating Custom User Model
  • Create Registration Form
  • Create Views for Registration, Login, and Logout
  • Create URLs for the Views
  • Testing User Registration
  • What's Next
  • Conclusion

Prerequisites

Before proceeding, ensure you:

  • Have basic knowledge of the Django framework.
  • Understand fundamentals of HTML and Bootstrap. If you need a refresher on HTML and Bootstrap, you can explore resources on w3schools.com.

Let's proceed

Understanding Authentication and Authorization

When building secure web applications or services, two key concepts often arise: authentication and authorization. Although these terms are sometimes used interchangeably, they refer to distinct processes with different roles.

Authentication is the act of confirming the identity of a user or system. It answers the question, “Who are you?” This process ensures that the entity attempting to gain access is legitimate. Common methods include username-password combinations, biometric scans, and multi-factor authentication (MFA)

Authorization, in contrast, determines what an authenticated user or system is allowed to do. It answers the question, “What can you access?” Authorization defines access levels, dictating which resources or actions are available to a particular user based on roles, permissions, or policies. For example, an authenticated user may log into a system, but their authorization level will determine whether they can view, edit, or delete certain data.

NOTE: While Django provides several authentication methods, including token-based and social authentication. For this guide, we will use Django's built-in authentication system, which primarily relies on session-based authentication.

Setting Up Your Environment

Before getting started, ensure you have Python and pip installed on your system. If Python is not installed, you can download and install it from the official Python website.

Create and Activate a Virtual Environment

It's best practice to use a virtual environment for your project to manage dependencies efficiently. Open your terminal in the desired location and run:

Python -m venv djangoLoginRegistration_env 
&& cd djangoLoginRegistration_env/bin/activate. 
Enter fullscreen mode Exit fullscreen mode

The command creates a virtual environment named djangoLoginRegistration_env and activates it.

This command is specific to Windows OS. If you're using a different operating system, search for instructions on how to create and activate a virtual environment for your OS. Once the virtual environment is set up, the remaining steps will be the same.

Install Django

Once the virtual environment is activated, install Django by running:

pip install Django
Enter fullscreen mode Exit fullscreen mode

Once you have Django installed, create a new project.

django-admin startproject djangoCustomLoginAndRegistrationSystem
cd djangoCustomLoginAndRegistrationSystem
Enter fullscreen mode Exit fullscreen mode

I named the project djangoCustomLoginAndRegistrationSystem. Admittedly, it’s a long name, so feel free to choose a different name for your project.

Next, create a new Django app that will handle the authentication logic. I’ll name it userauth

python manage.py startapp userauth
Enter fullscreen mode Exit fullscreen mode

Update settings.py

In your project settings, add userauth to the INSTALLED_APPS list to include the authentication app.

# django_auth_project/settings.py
INSTALLED_APPS = [
    …
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    'userauth.apps.UserauthConfig', # Add this line for the new auth app
]
Enter fullscreen mode Exit fullscreen mode

Start Your Server

Start the development server by running:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Navigate to http://127.0.0.1:8000/. At this stage, you’ll see Django’s default homepage, which is quite basic. In the next steps, we’ll create a more structured and visually appealing homepage.

Note: After starting the server, you might notice warnings about unapplied migrations. For now, you can ignore them—we’ll address migrations later when we extend the Django User model to include additional profile_picture and email fields.

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
January 18, 2025 - 07:38:10
Django version 4.2.13, using settings 'djangoCustomLoginAndRegistrationSystem.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Enter fullscreen mode Exit fullscreen mode

Creating an Index Page

Let's create an index page for our application, which will also serve as a test endpoint for redirection after logout.

Understanding Django Template files

Django provides two options for managing template files-at the project level and the app level.

We’ll use the app-level approach since it allows for better organization, especially when working with multiple apps—which is common in authentication systems. This approach also makes future expansion easier.

Setting Up the Template Structure

Inside the userauth app folder, create a new folder named templates. Within the templates folder, create another folder named userauth.

If you’re new to Django, this folder structure follows Django’s best practices for managing templates within individual apps.

Once the folders are set up, create a file named index.html and add the following content:

<!-- /userauth/templates/userauth/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django Auth Application</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
    .bg-custom-grey {
        background-color: #eeeeee; /* Light grey color */
    }

     /* Custom teal color for the heading */
        .text-teal {
            color: #008080; /* Teal color */
        }
</style>
</head>
<body>
    <!-- Navbar Section -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-0">
        <div class="container">
            <a class="navbar-brand" href="#">Auth App</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="#">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">Register</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

     <!-- Hero Section with Grey Background -->
    <section class="bg-custom-grey text-center py-5" style="height:370px;">
        <div class="container" style="padding-top: 40px;">
            <h1 class="display-4 text-teal">Django Authentication System</h1>
            <p class="lead"><em>A Bespoke user authentication system using Django's built-in authentication tools.</em></p>
            <a href="#" class="btn btn-light btn-lg">Learn More</a>
        </div>
    </section>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The code above defines a simple web page that utilizes Bootstrap 5 for styling and layout. It features a navigation bar and a hero section with a custom grey background

The highlights here are:

  • <a class="nav-link" href="#">Login</a>: A link to a login page.
  • <a class="nav-link" href="#">Register</a>: A link to a registration page.

Integrating the View into Django's MVC Architecture.

Django follows the MVC (Model-View-Controller) architecture, though it is commonly referred to as MVT (Model-View-Template) in Django terminology.

At this stage, we have created the necessary index template file, but we still need to define the view and set up the corresponding URL configuration (which acts as the controller) to complete this setup. This will allow us to render the page properly in the browser.

A model is not required at this point since we are not interacting with a database yet. However, as we progress and define a custom user model, we will fully implement the MVC/MVT architecture, integrating models for data management and persistence.

Let's start with the View Component

Define the View

Navigate to views.py inside the userauth app folder and add the following code:

# djangoCustomLoginAndRegistrationSystem/userauth/views.py 
from django.shortcuts import render

# Homepage view
def index(request):
    return render(request, 'userauth/index.html')
Enter fullscreen mode Exit fullscreen mode

Map the View to a URL

To make the view accessible, we need to define a URL route for it.
Create an urls.py file inside the userauth app folder (if it doesn’t already exist) and add the following code:

#djangoCustomLoginAndRegistrationSystem/userauth/urls.py 
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),  # Homepage route
]
Enter fullscreen mode Exit fullscreen mode

Register App URLs in the Project-Level urls.py

Next, we need to include the app-level urls.py in the project-level urls.py file so that Django recognizes our app's routes.

Open djangoCustomLoginAndRegistrationSystem/urls.py and update it as follows:

#djangoCustomLoginAndRegistrationSystem/urls.py 
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path('', include('userauth.urls')),  # Include `userauth` app URLs
]
Enter fullscreen mode Exit fullscreen mode

Start the Server

After completing all the steps, start the Django development server:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Now, visit http://127.0.0.1:8000/ in your browser. You should see the homepage successfully displayed.

Image description

Great!.

Now that our homepage is set up, let’s integrate the Model component.

Creating Custom User Model

At the core of Django's built-in authentication system is the User model. This model stores essential user information such as: username, first_name, last_name, date_joined, and more.

While Django’s default authentication system is fully functional, there are cases where you might need additional fields—such as phone numbers, or profile pictures.

To handle this, Django provides the AbstractUser class, which allows us to extend the default User Model and introduce custom fields without modifying Django’s built-in structure.

Since our system will include a profile picture and email, we’ll extend the AbstractUser model to include these fields.

The Custom User Model

Open the models.py file inside your userauth app.

Paste the following code:

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

class CustomUser(AbstractUser):
    profile_pic = models.ImageField(upload_to='profile_pics/', blank=True, null=True)
    email = models.EmailField(unique=True)  # Enforce unique emails

    # Fix reverse accessor conflicts with unique related names
    groups = models.ManyToManyField(
        'auth.Group',
        related_name='custom_users',
        blank=True,
        help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
        verbose_name='groups',
    )
    user_permissions = models.ManyToManyField(
        'auth.Permission',
        related_name='custom_users_permissions',
        blank=True,
        help_text='Specific permissions for this user.',
        verbose_name='user permissions',
    )
Enter fullscreen mode Exit fullscreen mode

Highlights:

The code above sets up a custom user model by extending Django’s AbstractUser class. This approach inherits all the standard functionality of AbstractUser while enhancing it with additional fields, such as profile_pic and email.

  • The profile_pic field stores the user's profile picture, using models.ImageField to handle image uploads. The upload_to='profile_pics/' argument specifies the directory where uploaded images will be saved.
  • Setting blank=True and null=True ensures these fields can remain empty if needed.

Additionally, including the groups and user_permissions fields in a custom user model allows for integration with Django’s built-in permission and group management systems.

Note: If you want to create an entirely new user model from scratch without retaining the default fields of Django’s user model, you should use the AbstractBaseUser class instead of AbstractUser. This provides complete control over the user model’s structure and behavior.

With this in place, we now have a custom user model that supports profile picture and email field. But before then, we have to configure Django to recognize and use this model as the default authentication model.

So, open settings.py and add this line:

AUTH_USER_MODEL = ' userauth.CustomUser'
Enter fullscreen mode Exit fullscreen mode

Now, it’s time to apply migrations.

For this demo, we’ll use SQLite, Django’s default database. However, you can easily switch to more robust databases like PostgreSQL or MySQL based on your project requirements.

To create the database, run the migrations by executing the following command:

python manage.py makemigrations userauth
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

This will set up the necessary database tables and prepare your application for use.

OUTPUT:

Migrations for 'userauth':
  userauth\migrations\0001_initial.py
    - Create model CustomUser

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, userauth
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying userauth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK
Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to create forms for user registration and login in userauth/forms.py.

Create Registration Form

Django's authentication system comes with built-in features like LoginView and LogoutView, along with their corresponding routes and base forms such as the AuthenticationForm. This setup simplifies the login and logout processes, requiring only the creation of template views.

However, Django does not provide a built-in solution for registration or sign-up forms. This means you’ll need to create a custom registration form to handle user sign-ups.

To achieve this, we'll create a CustomUserCreationForm by subclassing Django's generic UserCreationForm.

The UserCreationForm is designed to handle user creation but is tied to Django's default User model. By customizing it, we can adapt it to work with our custom user model- in this case, using an email address as the primary identifier.

So, inside your userauth app, create a new file named forms.py and add the following code:

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

class CustomUserCreationForm(UserCreationForm):
    email = forms.EmailField(required=True, max_length=254)  # Make email required

    class Meta:
        model = CustomUser
        fields = ['username', 'email', 'password1', 'password2']
        widgets = {
            'username': forms.TextInput(attrs={'class': 'form-control'}),
            'email': forms.EmailInput(attrs={'class': 'form-control'}),
            'password1': forms.PasswordInput(attrs={'class': 'form-control'}),
            'password2': forms.PasswordInput(attrs={'class': 'form-control'}),
        }
Enter fullscreen mode Exit fullscreen mode

Note: In the form, I included both username and email fields. Both fields are configured to be unique, and they will work seamlessly together because we extended the AbstractUser class. This approach retains the existing user model fields while adding new ones. Alternatively, you can simplify the registration process by using only the email field for sign-up and allowing users to choose a unique username later on their profile page.

Next Steps?

Now that we’ve created our registration form, the next steps are to:

  • Set up authentication views to handle registration, login, and logout.
  • Create corresponding HTML templates for user input and interaction.

Let’s move on to implementing the views!

Create Views for Registration, Login, and Logout

Before diving into the code, let’s quickly understand the workflow:

Registration Workflow:

  • When a user accesses the registration page, the RegisterView class handles the process. It renders the CustomUserCreationForm using the register.html template (which we’ll create later).

  • When the user submits the form, the system validates the input fields. If the form is valid, the user’s credentials are saved, and the user is automatically logged in and redirected to their profile page (profile.html), where a welcome message displays their username.

Profile Access Workflow:

  • Accessing the profile page requires authentication.

  • If a logged-in user accesses the profile page, the profile.html template is rendered.

  • If an unauthenticated user tries to access the profile page, they are redirected to the login page (login.html).

Login and Logout Workflow:

  • The CustomLoginView handles user login, rendering the login.html template.

  • Upon successful login, the user is redirected to their profile page.

  • The LogoutView handles user logout, redirecting the user to the homepage (index.html) after logout.

Implementing the Logic

Open your views.py file and add the following code:

# userauth/views.py
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.generic import TemplateView, CreateView
from django.contrib.auth import login
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from .forms import CustomUserCreationForm
from django.contrib.auth.views import LoginView

# Homepage view
def index(request):
    return render(request, 'userauth/index.html')

class RegisterView(SuccessMessageMixin, CreateView):
    template_name = 'userauth/register.html'
    form_class = CustomUserCreationForm
    success_url = reverse_lazy('profile')
    success_message = "Registration successful. Welcome!"

    def form_valid(self, form):
        # Save the user and log them in
        user = form.save()
        login(self.request, user)
        return super().form_valid(form)

class CustomLoginView(LoginView):
    template_name = 'userauth/login.html'

class ProfileView(LoginRequiredMixin, TemplateView):
    template_name = 'userauth/profile.html'
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code:

The above code uses CustomUserCreationForm to handle user registration, with a logic designed to automatically logs in the user after successful registration and redirects them to the profile page. For login access, CustomLoginView is called which renders the login.html template and subsequently redirect authenticated users to their profile page. The ProfileView requires authentication (using LoginRequiredMixin); renders the profile.html template and passes the logged-in user’s username to the context.(check auhenticity). Lastly, the CustomLogoutView handles user logout and redirects to the homepage (index.html).

Note: Django offers two types of views, - The function-based view and class-based view. I have used the class-based form here by subclassing the Django generic view CreateView class for the RegisterView and LoginView for CustomLoginView class

Now that we've completed the views, let's map their corresponding URLs to them.

Create URLs for the Views

Here, we'll define URLs for registration, login, and logout and profile views.

Update userauth/urls.py with the following code

# userauth/urls.py
from django.urls import path
from django.contrib.auth.views import LogoutView
from .views import RegisterView, ProfileView, CustomLoginView
from . import views

urlpatterns = [
    path('', views.index, name='index'),  # Home page view
    path('register/', RegisterView.as_view(), name='register'),
    path('profile/', ProfileView.as_view(), name='profile'),
    path('login/', CustomLoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(next_page='index'), name='logout'),  # Redirect to index page after logout
]
Enter fullscreen mode Exit fullscreen mode

Great! Now, let's integrate the final piece of the puzzle—creating template files—to complete our user authentication system. We'll start with the registration template.

Creating the Registration Template

Navigate to the userauth/templates/userauth/ directory and create a new file named register.html. Once created, add the following code inside it:

<!-- /userauth/templates/userauth/register.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Registration</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    {% if messages %}
    <div class="messages">
    <!--<div class="container mt-3">-->
        {% for message in messages %}
            <div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
                {{ message }}
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            </div>
        {% endfor %}
    </div>
    {% endif %}

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-0">
        <div class="container">
            <a class="navbar-brand" href="#">Auth App</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="#">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'register' %}">Register</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container-fluid vh-100 d-flex">
        <div class="col-lg-6 d-flex flex-column justify-content-center align-items-center">
            <h2 class="mb-4">Create an account</h2>
            <form id="register-form" class="w-75" method="post" action="{% url 'register' %}">
                {% csrf_token %}
                {{ form.as_p }}
                <div class="d-grid">
                    <button type="submit" class="btn btn-dark">Register</button>
                </div>
            </form>
            <p class="mt-3">Already have an account? <a href="{% url 'login' %}" class="text-dark fw-bold text-decoration-none">Log in</a></p>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now, start the development server:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Then, navigate to http://localhost:8000/register in your browser.

Image description

At this point, the registration page is displayed. However, the design is quite plain, and Django’s default warning messages don’t align with our intended UI. To achieve a better user experience, we will replace the existing register.html template with a more visually appealing one.

Replace register.html

Replace current register.html with the following code:

<!-- /userauth/templates/userauth/register.html -->

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Register</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container-fluid vh-100 d-flex">
        <!-- Left Section -->
        <div class="col-lg-6 bg-dark text-white d-flex flex-column justify-content-center align-items-center">
            <h1 class="mb-3">Pi-Health Hub</h1>
            <p class="mb-5">Create your account to get started</p>
            <img src="{% static 'assets/mri.png' %}" alt="Checkout Illustration" class="img-fluid" style="max-width: 400px;">
        </div>

        <!-- Right Section -->
        <div class="col-lg-6 d-flex flex-column justify-content-center align-items-center">
            <h2 class="mb-4">Create an account</h2>
            <form id="register-form" class="w-75" method="post">
                {% csrf_token %}
                <div class="mb-3">
                    <label for="username" class="form-label">Username</label>
                    <input type="text" id="username" name="username" class="form-control" placeholder="Choose a username" required>
                </div>
                <div class="mb-3">
                    <label for="email" class="form-label">Email Address</label>
                    <input type="email" id="email" name="email" class="form-control" placeholder="Enter your email" required>
                </div>
                <div class="mb-3">
                    <label for="password1" class="form-label">Password</label>
                    <input type="password" id="password1" name="password1" class="form-control" placeholder="Enter a password" required>
                </div>
                <div class="mb-3">
                    <label for="password2" class="form-label">Confirm Password</label>
                    <input type="password" id="password2" name="password2" class="form-control" placeholder="Confirm your password" required>
                </div>
                <div class="d-grid">
                    <button type="submit" class="btn btn-dark">Register</button>
                </div>
            </form>
            <p class="mt-3">Already have an account? <a href="{% url 'login' %}" class="text-dark fw-bold text-decoration-none">Log in</a></p>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Create login.html

Create a new file named login.html and add the following code:

<!-- /userauth/templates/userauth/login.html -->
{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container-fluid vh-100 d-flex">
        <!-- Left Section -->
        <div class="col-lg-6 bg-dark text-white d-flex flex-column justify-content-center align-items-center">
            <h1 class="mb-3">Pi-Health Hub</h1>
            <p class="mb-5">Login to access your account</p>
            <img src="{% static 'assets/mri.png' %}" alt="Checkout Illustration" class="img-fluid" style="max-width: 400px;">
        </div>

        <!-- Right Section -->
        <div class="col-lg-6 d-flex flex-column justify-content-center align-items-center">
            <h2 class="mb-4">Login to your account</h2>
            <form id="login-form" method="post" action="{% url 'login' %}" class="w-75">
                {% csrf_token %}
                <div class="mb-3">
                    <label for="id_username" class="form-label">Username</label>
                    <input type="text" name="username" id="id_username" class="form-control" placeholder="Enter your username" required>
                </div>
                <div class="mb-3 position-relative">
                    <label for="id_password" class="form-label">Password</label>
                    <input type="password" name="password" id="id_password" class="form-control" placeholder="Enter your password" required>
                    <br>
                    <a href="#" class="small position-absolute end-0 mt-1 text-decoration-none">Reset Password!</a>
                    <br>
                </div>
                {% if form.errors %}
                    <div class="alert alert-danger">
                        {% for field in form %}
                            {% for error in field.errors %}
                                <p>{{ error }}</p>
                            {% endfor %}
                        {% endfor %}
                        {% for error in form.non_field_errors %}
                            <p>{{ error }}</p>
                        {% endfor %}
                    </div>
                {% endif %}
                <div class="d-grid">
                    <button type="submit" class="btn btn-dark">Log in</button>
                </div>
            </form>
            <p class="mt-3">New user? <a href="{% url 'register' %}" class="text-dark fw-bold text-decoration-none">Create an account</a></p>

        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>   
Enter fullscreen mode Exit fullscreen mode

Create profile.html

Also, create a new file named profile.html and add the following code:

<!-- /userauth/templates/userauth/profile.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Profile</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-0">
        <div class="container">
            <a class="navbar-brand" href="#">Auth App</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
        {% if user.is_authenticated %}
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                        </li>

                        <!-- Display Profile Pic or Default Image if not available -->
                        <li class="nav-item">
                            <img 
                                src="{% if user.profile_pic %}{{ user.profile_pic.url }}{% else %}/static/assets/male-avatar.jpeg{% endif %}" 
                                alt="Profile Picture" 
                                class="profile-pic ms-2"
                                style="width: 40px; height: 40px; object-fit: cover; border-radius: 50%;">
                        </li>
        {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    <div class="container vh-100 d-flex" style="margin-top: 30px;">
    Welcome, {{ user.username }}!
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Also, we need to modify index.html template

Update index.html

Replace the content of index.html to conditionally display content based on the user's authentication status:

<!-- /userauth/templates/userauth/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django Auth Application</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
    .bg-custom-grey {
        background-color: #eeeeee; /* Light grey color */
    }

     /* Custom teal color for the heading */
        .text-teal {
            color: #008080; /* Teal color */
        }
</style>
</head>
<body>
    <!-- Navbar Section -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-0">
        <div class="container">
            <a class="navbar-brand" href="#">Auth App</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
         {% if user.is_authenticated %}
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                        </li>

                        <!-- Display Profile Pic or Default Image if not available -->
                        <li class="nav-item">
                            <img 
                                src="{% if user.profile_pic %}{{ user.profile_pic.url }}{% else %}/static/male-avatar.jpeg{% endif %}" 
                                alt="Profile Picture" 
                                class="profile-pic ms-2"
                                style="width: 40px; height: 40px; object-fit: cover; border-radius: 50%;">

                        </li>

                    {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'login' %}">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'register' %}">Register</a>
                    </li>
    {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    <!-- Hero Section with Grey Background -->
   <!-- Hero Section with Increased Height -->
<section class="bg-custom-grey text-center py-5" style="min-height: 350px; display: flex; align-items: center;">
    <div class="container" style="margin-top: -40px;">
    <h2 class="display-4 text-teal">Django Authentication System</h2>
    <p class="lead"><em>A Bespoke user authentication system using Django's built-in authentication tools.</em></p>
    <a href="#" class="btn btn-light btn-lg" style="margin-top: 25px;">Learn More</a>
</div>

</section>


    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Code Explanation:

The navigation bar in index.html dynamically adapts based on whether the user is authenticated or not:

  • If the user is authenticated, the navbar displays a logout option and the user’s profile picture (a default avatar is used if no picture is uploaded).
  • If the user is not logged in, it provides options to log in or register.
  • This dynamic rendering is made possible using Django’s template tags:
{% if user.is_authenticated %}
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                        </li>

                        <!-- Display Profile Pic or Default Image if not available -->
                        <li class="nav-item">
                            <img 
                                src="{% if user.profile_pic %}{{ user.profile_pic.url }}{% else %}/static/male-avatar.jpeg{% endif %}" 
                                alt="Profile Picture" 
                                class="profile-pic ms-2"
                                style="width: 40px; height: 40px; object-fit: cover; border-radius: 50%;">

                        </li>

                    {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'login' %}">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'register' %}">Register</a>
                    </li>
        {% endif %}
Enter fullscreen mode Exit fullscreen mode

One more thing—if you’ve noticed, I have imported {% load static %} at the top of the HTML files. This is necessary because we need to render static files, such as the profile picture avatar and the authentication interface image.

Steps to Set Up Static Files:

  1. Download the image files from here.
  2. Create a static folder inside the userauth folder.
  3. Inside the static folder, create another folder named assets.
  4. Extract the downloaded images into the assets folder.

That’s it! The images will now be displayed correctly in the specified locations in our HTML files.

One last thing, we need to specify login redirects in settings.py

Update Django Settings

To ensure users are redirected to their profile page after logging in, add the following line to settings.py:

# settings.py
LOGIN_REDIRECT_URL = '/profile/'  # Redirect to profile after login
Enter fullscreen mode Exit fullscreen mode

This tells Django to redirect authenticated users to the profile page after logging in.

Save and Rerun the Server

Save all the changes and restart the development server:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Now, you should have beautifully styled registration (http://localhost:8000/register) and login (http://localhost:8000/login) pages.

Registration Interface
Registration Interface

Login Interface
Login Interface

Testing User Registration

Navigate to the registration page (http://localhost:8000/register).

Fill in the required credentials (username, email, password, etc.). Ensure your username and password are unique and not too similar. Once done, click the Register button to create the user.

Oops! Encountering a 404 Error

Image description

After registering, you may encounter the following error:

Page not found (404)
Request Method: GET
Request URL: http://localhost:8000/accounts/login/?next=/profile/
Enter fullscreen mode Exit fullscreen mode

Understanding and Fixing the Error

Don’t panic! This error occurs because Django’s authentication system defaults to using /accounts/login/ for login redirection, which doesn’t exist in our setup.

To fix this, we need to explicitly set the login URL in settings.py:

#settings.py
LOGIN_URL = '/login/'
Enter fullscreen mode Exit fullscreen mode

Save the changes and restart the server again:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Now, everything should work smoothly!

Go ahead to test the registration, login, and profile access workflows again.

Register, and once successful, you’ll be redirect to your profile page with a welcome message.

Image description

What's Next

Congrats on making it this far! We’ve successfully built our custom authentication system, but there’s always room for improvement. Here are a few areas where you can enhance the system further:

  1. Refactoring Repetitive HTML Code: Some HTML structures are repeated across multiple pages. Instead of duplicating code, you can leverage Django’s template inheritance to maintain a cleaner, more efficient codebase. Create a base template that includes common elements (e.g., navbar, footer), and let other pages extend from it.
  2. Profile Picture Uploads: Currently, users are assigned a default profile picture, but they cannot update their photo. You can allow users to upload and manage their profile images through the authentication system.
  3. Enhancing Account Management: You can implement additional features such as password reset, email verification, and two-factor authentication (2FA) to improve security and user experience.
  4. User Permissions & Access Control: Once a user is authenticated, what actions should they be allowed to perform? You can implement role-based access control (RBAC) to define different levels of permissions for users (e.g., admin, editor, viewer).

You can download the full code from the GitHub repo

If you need further customizations or guidance, feel free to reach out. Happy coding!

Love the guide?

Your support will be appreciated

Buy Me A Coffee

Conclusion

In this guide, we've built a custom Django Login and Registration system, covering everything from setting up the project to implementing user authentication and profile functionality.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay