Login & Logout

Difficulty: Advanced

Django's authentication system provides both low-level functions and high-level views for handling user login and logout. Understanding both levels is important for implementing authentication correctly.

The authentication flow involves three steps: (1) verify credentials with authenticate(), (2) create a session with login(), and (3) redirect the user. authenticate() takes credentials (typically username and password), checks them against the database, and returns the User object if valid or None if invalid. login() takes the request and authenticated user, creates a session, and sets a session cookie in the browser.

For logout, the logout() function clears the session data and deletes the session cookie. After logout, request.user becomes an AnonymousUser instance. Always redirect after logout to prevent issues with cached pages.

Django provides built-in class-based views for authentication: LoginView, LogoutView, PasswordChangeView, PasswordChangeDoneView, PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, and PasswordResetCompleteView. These views handle all the logic - you only need to provide templates.

LoginView renders a login form, validates credentials, and logs the user in. It looks for a template at 'registration/login.html' by default. After successful login, it redirects to settings.LOGIN_REDIRECT_URL (default '/accounts/profile/'). You can customize the redirect with the 'next' parameter or by overriding get_success_url().

LogoutView logs the user out and redirects to settings.LOGOUT_REDIRECT_URL. It can also render a 'logged out' template if LOGOUT_REDIRECT_URL is not set.

The @login_required decorator (for FBVs) and LoginRequiredMixin (for CBVs) protect views from unauthenticated access. They redirect unauthenticated users to settings.LOGIN_URL (default '/accounts/login/') and pass a 'next' parameter so the user is redirected back after login.

Django sessions store authentication state between requests. When a user logs in, Django creates a session record in the database (or cache, depending on SESSION_ENGINE) and sends a session ID cookie to the browser. Each subsequent request includes this cookie, allowing Django to identify the user.

Code examples

Manual Login View

from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
from django import forms

class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)

def login_view(request):
    if request.user.is_authenticated:
        return redirect('dashboard')

    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            user = authenticate(
                request,
                username=form.cleaned_data['username'],
                password=form.cleaned_data['password'],
            )
            if user is not None:
                login(request, user)
                next_url = request.GET.get('next', 'dashboard')
                return redirect(next_url)
            else:
                form.add_error(None, 'Invalid username or password.')
    else:
        form = LoginForm()

    return render(request, 'accounts/login.html', {'form': form})

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

authenticate() verifies credentials. login() creates the session. The 'next' parameter preserves the intended destination. logout() clears the session. Always redirect authenticated users away from the login page.

Built-in Auth Views

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

urlpatterns = [
    path('accounts/login/',
         auth_views.LoginView.as_view(
             template_name='accounts/login.html',
         ),
         name='login'),

    path('accounts/logout/',
         auth_views.LogoutView.as_view(),
         name='logout'),

    path('accounts/password-change/',
         auth_views.PasswordChangeView.as_view(
             template_name='accounts/password_change.html',
             success_url='/accounts/password-change/done/',
         ),
         name='password-change'),

    path('accounts/password-change/done/',
         auth_views.PasswordChangeDoneView.as_view(
             template_name='accounts/password_change_done.html',
         ),
         name='password-change-done'),
]

# settings.py
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/dashboard/'
LOGOUT_REDIRECT_URL = '/'

Django's built-in auth views handle all authentication logic. You only provide templates. LOGIN_URL is where unauthenticated users are redirected. LOGIN_REDIRECT_URL is where users go after login.

Protecting Views

# Function-based: @login_required decorator
from django.contrib.auth.decorators import login_required

@login_required
def dashboard(request):
    return render(request, 'dashboard.html')

@login_required(login_url='/custom-login/')
def profile(request):
    return render(request, 'profile.html')

# Class-based: LoginRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

class DashboardView(LoginRequiredMixin, TemplateView):
    template_name = 'dashboard.html'
    login_url = '/accounts/login/'
    redirect_field_name = 'next'

# In templates: check authentication
# {% if user.is_authenticated %}
#   <p>Welcome, {{ user.username }}!</p>
#   <a href="{% url 'logout' %}">Logout</a>
# {% else %}
#   <a href="{% url 'login' %}">Login</a>
# {% endif %}

@login_required redirects unauthenticated users to LOGIN_URL with a 'next' parameter. LoginRequiredMixin does the same for CBVs. In templates, user.is_authenticated checks if the user is logged in.

Login Template

<!-- templates/accounts/login.html -->
{% extends 'base.html' %}

{% block content %}
<div class="login-form">
  <h2>Login</h2>

  {% if form.non_field_errors %}
    <div class="alert alert-danger">
      {% for error in form.non_field_errors %}
        <p>{{ error }}</p>
      {% endfor %}
    </div>
  {% endif %}

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

    <div class="field">
      <label for="{{ form.username.id_for_label }}">Username</label>
      {{ form.username }}
    </div>

    <div class="field">
      <label for="{{ form.password.id_for_label }}">Password</label>
      {{ form.password }}
    </div>

    <!-- Preserve the 'next' parameter -->
    {% if next %}
      <input type="hidden" name="next" value="{{ next }}">
    {% endif %}

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

  <p><a href="{% url 'password_reset' %}">Forgot password?</a></p>
</div>
{% endblock %}

The login template renders the authentication form. Non-field errors display invalid credential messages. The hidden 'next' input preserves the redirect destination. csrf_token is required for the POST form.

Key points

Concepts covered

login, logout, authenticate, LoginView, LogoutView, Sessions