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.