Introduction to Django’s Authentication System

This article provides a beginner-friendly introduction to Django’s authentication system. It explains users, passwords, sessions, login and logout, permissions, groups, built-in authentication views, access control for function-based and class-based views, registration, password management, and the role of custom user models.

Introduction to Django’s Authentication System

Most web applications need to know who is using them.

A website may allow visitors to read public pages while requiring an account to create posts, update profiles, place orders, or access administrative tools. Some authenticated users may have additional permissions that allow them to perform actions unavailable to everyone else.

Django includes an authentication system that provides the basic components needed to manage this behavior.

It supports:

  • user accounts
  • passwords
  • login and logout
  • cookie-based sessions
  • permissions
  • groups
  • authentication forms and views
  • access restrictions
  • customizable authentication backends

Django’s authentication framework is provided primarily by the django.contrib.auth application. It handles both authentication, which determines who a user is, and authorization, which determines what that user is allowed to do.

Authentication and Authorization

Authentication and authorization are related, but they answer different questions.

Authentication asks:

Who is this user?

Authorization asks:

What is this user allowed to do?

For example, a user may successfully log in with a username and password. That means the user has been authenticated.

The application may then check whether that user has permission to delete an article. That is authorization.

A user can therefore be:

  • unauthenticated
  • authenticated without a particular permission
  • authenticated with a particular permission
  • a staff user
  • a superuser

Django’s auth system provides tools for handling each of these cases.

The Main Parts of Django Authentication

The default authentication system is built around several related components.

Component Purpose
Users Represent people or accounts
Passwords Verify user credentials securely
Sessions Keep users logged in across requests
Permissions Allow or deny specific actions
Groups Apply permissions to multiple users
Authentication backends Decide how credentials are verified
Forms and views Support login, logout, and password management
Decorators and mixins Restrict access to views

These components work together, but they can also be customized separately.

Default Authentication Configuration

A project created with django-admin startproject normally includes the main authentication configuration.

The relevant installed applications include:

python

1
2
3
4
5
6
7
INSTALLED_APPS = [
    # ...
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    # ...
]

The relevant middleware includes:

python

1
2
3
4
5
6
MIDDLEWARE = [
    # ...
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    # ...
]

SessionMiddleware manages session data across requests.

AuthenticationMiddleware uses the session to associate a user with each incoming request. The standard Django project configuration includes these components by default.

Run migrations to create the required database tables:

bash

1
python manage.py migrate

This creates tables used for:

  • users
  • groups
  • permissions
  • sessions
  • content types
  • migration records

The Default User Model

Django provides a default user model named User.

It can be imported directly:

python

1
from django.contrib.auth.models import User

A user can contain information such as:

  • username
  • password
  • first name
  • last name
  • email address
  • staff status
  • active status
  • superuser status
  • groups
  • individual permissions
  • account creation date
  • last login date

A user might be created in the Django shell:

python

1
2
3
4
5
6
7
8
from django.contrib.auth.models import User


user = User.objects.create_user(
    username="alex",
    email="alex@example.com",
    password="secure-password",
)

The create_user() method is important because it processes the password correctly before saving it.

Do not create a password by assigning plain text directly:

python

1
2
3
4
5
6
user = User(
    username="alex",
    password="secure-password",
)

user.save()

This stores the value incorrectly and does not produce a usable Django password.

Use create_user():

python

1
2
3
4
user = User.objects.create_user(
    username="alex",
    password="secure-password",
)

Or use set_password():

python

1
2
3
user = User(username="alex")
user.set_password("secure-password")
user.save()

Password Hashing

Django does not normally store users’ original passwords.

Instead, it stores a derived password hash containing the information needed to verify a password later.

When a user attempts to log in, Django processes the submitted password and compares the result with the stored password data.

This means passwords should be handled through Django’s user methods:

python

1
2
user.set_password("new-password")
user.save()

Check a password with:

python

1
is_correct = user.check_password("submitted-password")

Avoid reading or comparing the password field directly:

python

1
2
if user.password == submitted_password:
    ...

That comparison will not work correctly because user.password contains encoded password information rather than the original password.

Creating a Superuser

A superuser has all permissions and can access the Django administration site when the admin application is configured.

Create one with:

bash

1
python manage.py createsuperuser

Django prompts for account information such as:

text

1
2
3
4
Username:
Email address:
Password:
Password confirmation:

The resulting account normally has:

python

1
2
user.is_staff is True
user.is_superuser is True

A superuser can usually manage users, groups, permissions, and registered models through the Django admin.

Accessing the Current User

Django adds the current user to the request object:

python

1
request.user

For a logged-in visitor, request.user is a user-model instance.

For a visitor who is not logged in, it is an AnonymousUser instance.

Check the user with:

python

1
2
3
4
5
def dashboard(request):
    if request.user.is_authenticated:
        username = request.user.get_username()
    else:
        username = "Guest"

is_authenticated is a property, not a method.

Correct:

python

1
request.user.is_authenticated

Incorrect:

python

1
request.user.is_authenticated()

Django uses sessions and authentication middleware to provide request.user. An unauthenticated request receives AnonymousUser; an authenticated request receives the relevant user instance.

AnonymousUser

An unauthenticated visitor is represented by AnonymousUser.

This allows code to access request.user without first checking whether a user object exists.

For example:

python

1
2
3
4
5
def home(request):
    if request.user.is_authenticated:
        message = f"Welcome back, {request.user.get_username()}."
    else:
        message = "Welcome, visitor."

Useful differences include:

text

1
2
3
4
5
6
7
Authenticated user:
    is_authenticated → True
    is_anonymous     → False

AnonymousUser:
    is_authenticated → False
    is_anonymous     → True

Prefer checking:

python

1
request.user.is_authenticated

rather than checking the user’s exact class.

Authenticating Credentials

Django provides the authenticate() function for checking credentials.

python

1
2
3
4
5
6
7
8
from django.contrib.auth import authenticate


user = authenticate(
    request,
    username="alex",
    password="secure-password",
)

If the credentials are accepted, authenticate() returns a user object.

If authentication fails, it returns None.

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
user = authenticate(
    request,
    username=username,
    password=password,
)

if user is not None:
    # Credentials were accepted.
    ...
else:
    # Credentials were rejected.
    ...

Authentication does not automatically log the user in.

It only verifies the credentials and returns the matching user.

Logging a User In

Use Django’s login() function to attach an authenticated user to the current session.

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.contrib.auth import authenticate, login


def login_view(request):
    username = request.POST["username"]
    password = request.POST["password"]

    user = authenticate(
        request,
        username=username,
        password=password,
    )

    if user is not None:
        login(request, user)

The common sequence is:

text

1
2
3
4
5
6
7
8
9
Submitted credentials
        ↓
authenticate()
        ↓
User object or None
        ↓
login()
        ↓
User ID stored in session

login() records the authenticated user in Django’s session framework so the user remains associated with later requests.

A Basic Login View

A complete basic login view might look like this:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render


def login_view(request):
    error_message = None

    if request.method == "POST":
        username = request.POST.get("username", "")
        password = request.POST.get("password", "")

        user = authenticate(
            request,
            username=username,
            password=password,
        )

        if user is not None:
            login(request, user)
            return redirect("dashboard")

        error_message = "Invalid username or password."

    return render(
        request,
        "accounts/login.html",
        {"error_message": error_message},
    )

A simple template could be:

django

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<h1>Log in</h1>

{% if error_message %}
    <p>{{ error_message }}</p>
{% endif %}

<form method="post">
    {% csrf_token %}

    <label for="username">Username</label>
    <input
        id="username"
        name="username"
        type="text"
        required
    >

    <label for="password">Password</label>
    <input
        id="password"
        name="password"
        type="password"
        required
    >

    <button type="submit">Log in</button>
</form>

This example demonstrates the basic process, but Django also provides built-in forms and views that avoid rewriting standard authentication behavior.

Logging a User Out

Use logout() to end the authenticated session:

python

1
2
3
4
5
6
7
from django.contrib.auth import logout
from django.shortcuts import redirect


def logout_view(request):
    logout(request)
    return redirect("home")

After logout:

python

1
request.user.is_authenticated

will be False on subsequent requests.

Logout should normally be triggered by a POST request rather than a plain link using GET.

Example:

python

1
2
3
4
5
6
7
8
9
from django.contrib.auth import logout
from django.shortcuts import redirect
from django.views.decorators.http import require_POST


@require_POST
def logout_view(request):
    logout(request)
    return redirect("home")

Template:

django

1
2
3
4
<form method="post" action="{% url 'logout' %}">
    {% csrf_token %}
    <button type="submit">Log out</button>
</form>

Django’s Built-In Authentication Views

Django includes class-based views for common authentication tasks.

These include views for:

  • login
  • logout
  • changing passwords
  • resetting forgotten passwords
  • confirming password resets

They are available from:

python

1
django.contrib.auth.views

Common classes include:

python

1
2
3
4
5
6
7
8
LoginView
LogoutView
PasswordChangeView
PasswordChangeDoneView
PasswordResetView
PasswordResetDoneView
PasswordResetConfirmView
PasswordResetCompleteView

Using the built-in views reduces repeated authentication code and uses Django’s standard behavior.

Adding Authentication URLs

Django provides a ready-made authentication URL configuration.

Include it in the project URL configuration:

python

1
2
3
4
5
6
from django.urls import include, path


urlpatterns = [
    path("accounts/", include("django.contrib.auth.urls")),
]

This creates named routes such as:

text

1
2
3
4
5
6
7
8
accounts/login/
accounts/logout/
accounts/password_change/
accounts/password_change/done/
accounts/password_reset/
accounts/password_reset/done/
accounts/reset/<uidb64>/<token>/
accounts/reset/done/

The exact routes are provided by Django’s auth URL configuration.

This approach gives the application standard authentication behavior while allowing custom templates.

Login Templates

Django’s built-in LoginView uses this template by default:

text

1
registration/login.html

A project structure might contain:

text

1
2
3
4
project/
├── templates/
│   └── registration/
│       └── login.html

Example template:

django

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<h1>Log in</h1>

{% if form.errors %}
    <p>Your username or password was incorrect.</p>
{% endif %}

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}

    <button type="submit">Log in</button>
</form>

Ensure the project template directory is configured:

python

1
2
3
4
5
6
7
TEMPLATES = [
    {
        # ...
        "DIRS": [BASE_DIR / "templates"],
        # ...
    },
]

Configuring Login Redirects

After login, Django may redirect the user to the URL provided in the next parameter.

For example:

text

1
/accounts/login/?next=/dashboard/

After successful authentication, the user is redirected to:

text

1
/dashboard/

A default redirect can be configured in settings.py:

python

1
LOGIN_REDIRECT_URL = "dashboard"

The login page can also be configured:

python

1
LOGIN_URL = "login"

The logout redirect can be configured with:

python

1
LOGOUT_REDIRECT_URL = "home"

Using named URL patterns keeps the configuration independent of hard-coded paths.

Restricting Function-Based Views

Use the login_required decorator to prevent unauthenticated visitors from accessing a function-based view.

python

1
2
3
4
5
6
7
from django.contrib.auth.decorators import login_required
from django.shortcuts import render


@login_required
def dashboard(request):
    return render(request, "accounts/dashboard.html")

When an unauthenticated visitor requests this page, Django redirects them to the configured login URL.

The original destination is added as a next parameter:

text

1
/accounts/login/?next=/dashboard/

After login, the user can be returned to the requested page.

A custom login URL can be supplied:

python

1
2
3
@login_required(login_url="custom-login")
def dashboard(request):
    ...

Restricting Class-Based Views

Use LoginRequiredMixin with class-based views:

python

1
2
3
4
5
6
7
8
9
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView


class DashboardView(
    LoginRequiredMixin,
    TemplateView,
):
    template_name = "accounts/dashboard.html"

The mixin should normally appear before the main view class:

python

1
2
3
4
5
class DashboardView(
    LoginRequiredMixin,
    TemplateView,
):
    ...

URL pattern:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from django.urls import path

from .views import DashboardView


urlpatterns = [
    path(
        "dashboard/",
        DashboardView.as_view(),
        name="dashboard",
    ),
]

Authentication Checks in Templates

When Django’s authentication context processor is enabled, templates can access the current user through:

django

1
{{ user }}

Check whether the user is authenticated:

django

1
2
3
4
5
{% if user.is_authenticated %}
    <p>Welcome, {{ user.get_username }}.</p>
{% else %}
    <p>You are not logged in.</p>
{% endif %}

Show login or logout controls:

django

1
2
3
4
5
6
7
8
{% if user.is_authenticated %}
    <form method="post" action="{% url 'logout' %}">
        {% csrf_token %}
        <button type="submit">Log out</button>
    </form>
{% else %}
    <a href="{% url 'login' %}">Log in</a>
{% endif %}

Authentication checks in templates control presentation.

They do not secure a view by themselves.

This is not sufficient:

django

1
2
3
{% if user.is_authenticated %}
    <a href="{% url 'dashboard' %}">Dashboard</a>
{% endif %}

The dashboard view must also enforce authentication with login_required or LoginRequiredMixin.

Hiding a link is not the same as protecting the destination.

Registering Users

Django includes authentication views, but it does not automatically add a complete public registration workflow to every project.

A basic registration form can use UserCreationForm:

python

1
from django.contrib.auth.forms import UserCreationForm

Example view:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import redirect, render


def register(request):
    if request.method == "POST":
        form = UserCreationForm(request.POST)

        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect("dashboard")
    else:
        form = UserCreationForm()

    return render(
        request,
        "registration/register.html",
        {"form": form},
    )

Template:

django

1
2
3
4
5
6
7
8
<h1>Create an account</h1>

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}

    <button type="submit">Register</button>
</form>

URL pattern:

python

1
path("accounts/register/", register, name="register")

UserCreationForm handles:

  • username validation
  • password confirmation
  • password validation
  • secure password processing
  • user creation

User Status Fields

The default user model includes several status fields.

is_active

Indicates whether the account should be treated as active:

python

1
user.is_active

An inactive account is usually prevented from authenticating by Django’s default authentication backend.

An account can be disabled without deleting it:

python

1
2
user.is_active = False
user.save(update_fields=["is_active"])

This can be useful when account history and related records should be preserved.

is_staff

Controls whether a user may access the Django admin:

python

1
user.is_staff

Staff status alone does not automatically grant every permission.

is_superuser

Indicates that the user has all permissions without requiring them to be assigned individually:

python

1
user.is_superuser

A common misunderstanding is that every authenticated user is a staff user.

These states are separate:

text

1
2
3
4
5
6
7
8
Authenticated user:
    Has successfully logged in

Staff user:
    May access the Django admin

Superuser:
    Has all permissions

Permissions

Django includes a model-level permission system.

For each model, Django creates standard permissions such as:

text

1
2
3
4
add
change
delete
view

For a model named Article in an app named articles, the permissions are typically:

text

1
2
3
4
articles.add_article
articles.change_article
articles.delete_article
articles.view_article

Check a permission with:

python

1
request.user.has_perm("articles.change_article")

Example:

python

1
2
3
def edit_article(request, article_id):
    if not request.user.has_perm("articles.change_article"):
        ...

A user can receive permissions:

  • directly
  • through one or more groups
  • automatically as a superuser

Restricting Views by Permission

For function-based views, use permission_required:

python

1
2
3
4
5
6
7
8
9
from django.contrib.auth.decorators import permission_required


@permission_required(
    "articles.add_article",
    raise_exception=True,
)
def create_article(request):
    ...

With raise_exception=True, Django raises PermissionDenied when the user lacks the permission.

Without it, the user may be redirected to the login page.

For class-based views, use PermissionRequiredMixin:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import CreateView

from .models import Article


class ArticleCreateView(
    PermissionRequiredMixin,
    CreateView,
):
    model = Article
    fields = ["title", "content"]
    permission_required = "articles.add_article"

Multiple permissions can be required:

python

1
2
3
4
permission_required = (
    "articles.view_article",
    "articles.change_article",
)

Groups

A group is a collection of permissions that can be assigned to multiple users.

Examples of application groups might include:

text

1
2
3
4
Editors
Moderators
Support Agents
Store Managers

Instead of assigning the same permissions to every editor individually, create an Editors group and assign permissions to that group.

Users added to the group receive its permissions.

python

1
2
3
4
5
from django.contrib.auth.models import Group


editors = Group.objects.get(name="Editors")
user.groups.add(editors)

Check group membership:

python

1
2
3
is_editor = request.user.groups.filter(
    name="Editors",
).exists()

However, permission checks are usually preferable to group-name checks when access depends on a specific capability.

Prefer:

python

1
request.user.has_perm("articles.change_article")

over:

python

1
request.user.groups.filter(name="Editors").exists()

The permission check describes what the user may do rather than how the permission was assigned.

Adding Permissions to a User

A permission can be assigned directly:

python

1
2
3
4
5
6
7
8
from django.contrib.auth.models import Permission


permission = Permission.objects.get(
    codename="change_article",
)

user.user_permissions.add(permission)

Remove it with:

python

1
user.user_permissions.remove(permission)

Retrieve all effective permissions:

python

1
permissions = user.get_all_permissions()

Check several permissions:

python

1
2
3
4
5
6
has_permissions = user.has_perms(
    [
        "articles.view_article",
        "articles.change_article",
    ]
)

Custom Permissions

Models can define additional permissions through Meta.

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.db import models


class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

    class Meta:
        permissions = [
            (
                "publish_article",
                "Can publish articles",
            ),
        ]

Create and apply a migration:

bash

1
2
python manage.py makemigrations
python manage.py migrate

The permission can then be checked with:

python

1
2
3
request.user.has_perm(
    "articles.publish_article",
)

Custom permissions are useful for domain-specific actions that do not match the standard add, change, delete, and view permissions.

Authentication Backends

An authentication backend determines how Django verifies credentials and checks permissions.

The default backend is commonly:

python

1
2
3
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
]

ModelBackend authenticates against Django’s user model and supports Django’s standard permission system.

A project can define or install other backends for cases such as:

  • email-based login
  • company directory authentication
  • external identity providers
  • custom account databases
  • remote-user authentication

The authenticate() function tries the configured authentication backends until one accepts the supplied credentials.

For a basic project, the default backend is usually sufficient.

Using the Configured User Model

Django allows projects to replace the default user model.

For code that refers to the user model, prefer:

python

1
2
3
4
from django.contrib.auth import get_user_model


User = get_user_model()

For model relationships, use:

python

1
2
3
4
5
6
7
8
9
from django.conf import settings
from django.db import models


class Article(models.Model):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

Avoid hard-coding the default user model in reusable code:

python

1
from django.contrib.auth.models import User

Directly importing User may be acceptable in a small project that definitely uses Django’s default model, but get_user_model() and settings.AUTH_USER_MODEL are more flexible.

Use:

text

1
2
3
4
5
Runtime Python code:
    get_user_model()

Model relationship:
    settings.AUTH_USER_MODEL

Choosing a Custom User Model Early

Projects frequently need additional account behavior, such as:

  • login by email address
  • additional profile fields
  • different required fields
  • a different user identifier
  • application-specific account methods

Django supports custom user models, but changing the user model after a project has accumulated migrations and data can be difficult.

For a new production project, it is worth deciding early whether a custom user model will be needed.

A common minimal approach extends AbstractUser:

python

1
2
3
4
5
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass

Then configure it before the initial migrations:

python

1
AUTH_USER_MODEL = "accounts.User"

Even an initially empty custom model provides a place for future account customization.

A full explanation of custom user models is a separate topic. Beginners should first understand Django’s default authentication workflow.

Password Change and Reset

Django includes views for changing a known password and resetting a forgotten password.

Password change is for a user who is already authenticated and knows the current password.

Password reset is for a user who cannot log in and needs a reset link sent by email.

The built-in URL configuration provides both workflows:

python

1
2
3
4
path(
    "accounts/",
    include("django.contrib.auth.urls"),
)

Password reset requires email configuration because Django must send the reset link.

For local development, email can be written to the terminal:

python

1
2
3
EMAIL_BACKEND = (
    "django.core.mail.backends.console.EmailBackend"
)

A reset email will then appear in the development server output instead of being sent through a real mail provider.

Sessions and Authentication

Django normally keeps users logged in with sessions.

After successful login, the session contains information Django can use to identify the authenticated user on later requests.

The browser receives a session cookie.

On later requests:

  1. the browser sends the session cookie
  2. SessionMiddleware loads the session
  3. AuthenticationMiddleware finds the associated user
  4. Django assigns that user to request.user

The browser’s cookie does not normally contain the user’s password.

The session connects the request to server-side authentication state.

CSRF Protection

Authentication forms that submit data should include CSRF protection.

Example:

django

1
2
3
4
5
6
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}

    <button type="submit">Submit</button>
</form>

This applies to forms for:

  • login
  • logout
  • registration
  • password changes
  • profile updates
  • permission-protected actions

CSRF protection helps prevent another website from submitting an unwanted request using a user’s authenticated browser session.

Authentication Is Not Template Visibility

A frequent beginner mistake is securing only the user interface.

For example:

django

1
2
3
4
5
{% if user.is_authenticated %}
    <a href="{% url 'article-create' %}">
        Create article
    </a>
{% endif %}

This hides the link from anonymous visitors, but it does not prevent them from manually visiting the URL.

The view must enforce the rule:

python

1
2
3
@login_required
def create_article(request):
    ...

Or:

python

1
2
3
4
5
class ArticleCreateView(
    LoginRequiredMixin,
    CreateView,
):
    ...

The same principle applies to permissions.

A hidden button is a presentation decision.

A decorator, mixin, or explicit server-side check is an access-control decision.

Common Beginner Mistakes

Storing Plain-Text Passwords

Avoid:

python

1
2
user.password = "secret"
user.save()

Use:

python

1
2
user.set_password("secret")
user.save()

Or:

python

1
2
3
4
User.objects.create_user(
    username="alex",
    password="secret",
)

Calling is_authenticated

Incorrect:

python

1
request.user.is_authenticated()

Correct:

python

1
request.user.is_authenticated

Assuming Authentication Grants Every Permission

A logged-in user does not automatically have permission to edit or delete every object.

Check the required permissions or ownership rules.

Protecting Only the Template

Hiding links does not protect URLs.

Always enforce authentication and authorization in the view.

Forgetting AuthenticationMiddleware

Without the correct middleware, request.user will not behave as expected.

Importing the Default User Everywhere

Prefer get_user_model() and settings.AUTH_USER_MODEL when the code should support a custom user model.

Changing the User Model Late

Choose a custom user model near the beginning of a new project when customization is likely.

Writing a Complete Login System From Scratch

Django already provides tested forms, views, password handling, sessions, and reset workflows.

Customize the built-in components before replacing them.

Revealing Too Much in Login Errors

A generic message is often preferable:

text

1
Invalid username or password.

Avoid confirming whether a particular username or email address exists unless the workflow requires it.

A Basic Authentication Setup

A small project can use Django’s built-in auth views with the following setup.

Project URLs:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path("admin/", admin.site.urls),
    path(
        "accounts/",
        include("django.contrib.auth.urls"),
    ),
    path("", include("core.urls")),
]

Settings:

python

1
2
3
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "dashboard"
LOGOUT_REDIRECT_URL = "home"

Login template:

text

1
2
3
templates/
└── registration/
    └── login.html
django

1
2
3
4
5
6
7
8
<h1>Log in</h1>

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}

    <button type="submit">Log in</button>
</form>

Protected function-based view:

python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.contrib.auth.decorators import login_required
from django.shortcuts import render


@login_required
def dashboard(request):
    return render(
        request,
        "core/dashboard.html",
    )

Protected class-based view:

python

1
2
3
4
5
6
7
8
9
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView


class DashboardView(
    LoginRequiredMixin,
    TemplateView,
):
    template_name = "core/dashboard.html"

Navigation template:

django

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<nav>
    <a href="{% url 'home' %}">Home</a>

    {% if user.is_authenticated %}
        <a href="{% url 'dashboard' %}">
            Dashboard
        </a>

        <form
            method="post"
            action="{% url 'logout' %}"
        >
            {% csrf_token %}
            <button type="submit">
                Log out
            </button>
        </form>
    {% else %}
        <a href="{% url 'login' %}">
            Log in
        </a>
    {% endif %}
</nav>

This provides a basic login, logout, and protected-page workflow.

A practical order for learning Django authentication is:

  1. Understand request.user.
  2. Check is_authenticated.
  3. Create users with create_user().
  4. Use Django’s built-in login and logout views.
  5. Protect views with login_required.
  6. Protect class-based views with LoginRequiredMixin.
  7. Build a registration page with UserCreationForm.
  8. Learn model permissions.
  9. Learn groups.
  10. Add password reset.
  11. Learn when to use a custom user model.
  12. Explore custom authentication backends only when needed.

Start with Django’s default behavior before adding external authentication packages or custom account logic.

Mini Reference

text

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
django.contrib.auth
    Main authentication application

request.user
    Current user or AnonymousUser

request.user.is_authenticated
    Whether the current user is logged in

authenticate()
    Verify submitted credentials

login()
    Attach an authenticated user to the session

logout()
    End the authenticated session

login_required
    Protect a function-based view

LoginRequiredMixin
    Protect a class-based view

permission_required
    Require permission in a function-based view

PermissionRequiredMixin
    Require permission in a class-based view

UserCreationForm
    Create a user with password validation

get_user_model()
    Retrieve the configured user model

settings.AUTH_USER_MODEL
    Reference the configured user model in relationships

Common settings:

python

1
2
3
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "dashboard"
LOGOUT_REDIRECT_URL = "home"

Common permission check:

python

1
2
3
request.user.has_perm(
    "articles.change_article",
)

Common user relationship:

python

1
2
3
4
author = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    on_delete=models.CASCADE,
)

Django’s authentication system provides the basic infrastructure needed to identify users and control access to application features.

The main concepts are:

  • authentication establishes who the user is
  • authorization determines what the user may do
  • users are connected to requests through sessions and middleware
  • request.user represents the current user
  • authenticate() verifies credentials
  • login() begins an authenticated session
  • logout() ends the session
  • decorators and mixins protect views
  • permissions describe allowed actions
  • groups assign permissions to multiple users
  • Django provides built-in forms and views for standard account workflows
  • password values should always be handled through Django’s password tools
  • custom user models should be considered early in a project

For most basic applications, Django’s built-in authentication components provide a secure and practical starting point. Learn the default system first, configure the existing views and forms, and add customization only when the application has a clear requirement for it.

Join the Newsletter

Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.

Get updates when new guides, learning paths, cheat sheets, and field notes are published.

No spam. Unsubscribe anytime.



There is no third-party involved so don't worry - we won't share your details with anyone.