User Authentication

Up to now, every TaskFlow view has treated every visitor identically — there's been no notion of "who is looking at this." Real task-management apps need that notion badly: TaskFlow's whole premise is that projects have an owner, and a task list should mean something different depending on who's logged in. Handling logins yourself — hashing and checking passwords, managing session cookies, protecting against timing attacks — is exactly the kind of security-sensitive plumbing you do not want to write from scratch. Django agrees, and ships a complete authentication system, django.contrib.auth, ready to use: a User model, working login and logout views, and password hashing already wired up correctly. This lesson covers using that system for real inside TaskFlow.

What django.contrib.auth Gives You for Free

django.contrib.auth is one of Django's default installed apps — it's already active in a new project's INSTALLED_APPS, with no setup required beyond running migrations. It provides a User model with the fields you'd expect (username, email, password — stored as a salted hash, never in plain text), along with ready-made views for logging in and logging out, and the session machinery that remembers a visitor is logged in from one request to the next.

Wiring Up LoginView and LogoutView

LoginView and LogoutView are class-based views that already know how to show a login form, check submitted credentials, and manage the session — you don't write the logic, only point a URL at them:

# taskflow_project/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

That's the entire URL setup — no view function to write for either one. LogoutView in particular needs nothing further at all: visiting it logs the current session out and redirects, with no template of its own required.

The Login Template Django Expects

LoginView does expect one thing from you: a template. By default it looks for registration/login.html, and renders a form variable into it — Django's own built-in authentication form, ready to render the same way any other form is:

{# taskflow/templates/registration/login.html #}
{% extends 'taskflow/base.html' %}

{% block content %}
<h2>Log In</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Log In</button>
</form>
{% endblock %}

That registration/ folder name isn't arbitrary — it's the specific path LoginView looks for unless you override it, so the template needs to live at exactly that location inside one of your apps' templates/ folders.

Protecting a View: login_required and LoginRequiredMixin

Some TaskFlow pages should only be reachable by a logged-in visitor — creating a task is a good example, since a task needs to belong to someone. For a function-based view, the @login_required decorator handles this in one line, placed directly above the view function:

# taskflow/views.py
@login_required
def task_create(request):
    # only ever reached by an authenticated visitor —
    # anyone else is redirected to the login page automatically
    ...

For a class-based view, the equivalent is LoginRequiredMixin, added as a base class alongside whatever generic view you're already using:

# taskflow/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
from .models import Task

class TaskCreateView(LoginRequiredMixin, CreateView):
    model = Task
    fields = ['title', 'description', 'due_date', 'priority']

Both approaches do the same thing under the hood: if the visitor isn't logged in, Django redirects them to the login page before the view's own code ever runs, and sends them right back to the page they originally wanted once they've logged in.

request.user Inside a View

Every incoming request carries a request.user attribute — the currently logged-in User instance, or a special anonymous-user object if nobody's logged in. Checking request.user.is_authenticated is the standard way to branch behavior for logged-in versus anonymous visitors without forcing a login on every page:

# taskflow/views.py
def task_list(request):
    if request.user.is_authenticated:
        tasks = Task.objects.filter(project__owner=request.user)
    else:
        tasks = Task.objects.none()

    return render(request, 'taskflow/task_list.html', {'tasks': tasks})

This is exactly the pattern that makes form.save(commit=False), from ModelForms, useful in practice — setting a new task's project owner to request.user before the final .save().

The Current User in a Template

Templates get the same information through the user variable, made available automatically on every request (via Django's built-in context processor). A typical nav bar branches on user.is_authenticated exactly the way a view branches on request.user.is_authenticated:

<nav>
    {% if user.is_authenticated %}
        <span>Hi, {{ user.username }}</span>
        <a href="{% url 'logout' %}">Log Out</a>
    {% else %}
        <a href="{% url 'login' %}">Log In</a>
    {% endif %}
</nav>

LOGIN_URL and LOGIN_REDIRECT_URL

Two settings control the redirect behavior described above. LOGIN_URL tells Django (and login_required / LoginRequiredMixin) where to send an unauthenticated visitor; LOGIN_REDIRECT_URL tells Django where a visitor should land right after a successful login, if they didn't arrive at the login page from some other protected page first.

# taskflow_project/settings.py
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'task-list'

Both values here are URL names, matching the name= arguments given to path() earlier — not hardcoded paths — so they keep working even if the actual URL strings change later.

Next up: Logging in assumes an account already exists. Django doesn't ship a sign-up page out of the box — User Registration covers building one, next.