User Authentication

Reviewed & published by Brayan K

Django's authentication system manages user accounts, logging in, and logging out — it verifies a username and password with authenticate(), starts a session with login(), ends it with logout(), and exposes the current user on every request as request.user.

Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll create users the safe way with hashed passwords, sign people in and out, check whether a visitor is authenticated, and wire up Django's ready-made login and registration tools so you don't have to build them by hand.

What You'll Learn in This Lesson

1 The User Model and Creating Users

Django ships with a built-in User model that lives in django.contrib.auth.models. Each user has fields like username, email, password, is_active, and is_staff. You almost never write this model yourself — Django created it and its database table for you when you ran your first migration.

The single most important rule of authentication: never set the password field directly. Always create accounts with User.objects.create_user(), which hashes the password before saving it. Storing a raw password is a serious security bug.

from django.contrib.auth.models import User

# The RIGHT way: create_user() hashes the password for you
user = User.objects.create_user(
    username="alice",
    email="alice@example.com",
    password="s3cret-pass",   # hashed automatically before saving
)

# Need an admin/superuser? There's a helper for that too:
admin = User.objects.create_superuser(
    username="root",
    email="root@example.com",
    password="another-secret",
)

print(user.username)            # "alice"
print(user.password[:7])        # "pbkdf2_" — a hash, never the raw text
# Worked example: a tiny simulation of what create_user() does.
# We "hash" passwords with a fake one-way function so you can SEE
# that the stored value is never the raw password.

import hashlib

users = {}  # our pretend database table

def fake_hash(raw_password):
    # A real Django hash is pbkdf2_sha256$... — this is a stand-in.
    digest = hashlib.sha256(raw_password.encode()).hexdigest()
    return "pbkdf2_sha256$" + digest

def create_user(username, password):
    users[username] = {"username": username, "password": fake_hash(password)}
    return users[username]

alice = create_user("alice", "s3cret-pass")
print("username:", alice["username"])
print("stored  :", alice["password"][:25], "...")
print("raw text never stored:", "s3cret-pass" not in alice["password"])

# EXPECTED: the stored value starts with pbkdf2_sha256$ and
# the raw password text is NOT inside it.

# ✅ Expected output:
# username: alice
# stored  : pbkdf2_sha256$926d3a2dd68 ...
# raw text never stored: True

2 Logging In, Logging Out, and request.user

Signing someone in is two steps. First authenticate() checks the credentials and returns the matching User (or None if they are wrong). Then login() takes that user and starts a session so they stay signed in. To sign someone out, call logout().

from django.contrib.auth import authenticate, login, logout

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

    # Step 1: verify the credentials
    user = authenticate(request, username=username, password=password)

    if user is not None:
        # Step 2: start the session
        login(request, user)
        return redirect("dashboard")
    else:
        # authenticate() returned None — bad username or password
        return render(request, "login.html", {"error": "Invalid login"})

def logout_view(request):
    logout(request)            # ends the session
    return redirect("home")

On every request, Django gives you request.user. For a signed-in visitor it is their User object; for an anonymous visitor it is an AnonymousUser. Check request.user.is_authenticated to tell them apart.

def dashboard(request):
    if request.user.is_authenticated:
        return HttpResponse(f"Welcome back, {request.user.username}!")
    else:
        return redirect("login")
# Worked example: a runnable simulation of authenticate() + login().
# We model a user store and a session as plain dictionaries.

import hashlib

def fake_hash(raw):
    return "pbkdf2_sha256$" + hashlib.sha256(raw.encode()).hexdigest()

# Pretend database: usernames -> hashed passwords
user_store = {"alice": fake_hash("s3cret-pass")}
session = {}  # stands in for request.session

def authenticate(username, password):
    """Return the username if credentials match, else None."""
    stored = user_store.get(username)
    if stored is not None and stored == fake_hash(password):
        return username
    return None

def login(session, username):
    session["user"] = username  # Django stores the user id in the session

def logout(session):
    session.pop("user", None)

# Good credentials
user = authenticate("alice", "s3cret-pass")
print("authenticate result:", user)
if user is not None:
    login(session, user)
print("is_authenticated:", "user" in session)

# Bad credentials -> None
print("bad password gives:", authenticate("alice", "wrong"))

logout(session)
print("after logout, is_authenticated:", "user" in session)

# ✅ Expected output:
# authenticate result: alice
# is_authenticated: True
# bad password gives: None
# after logout, is_authenticated: False

3 Built-in Auth Views and Registration

You rarely need to write login logic by hand. Django provides LoginView and LogoutView, and including django.contrib.auth.urls in your URLconf wires up the whole login, logout, and password-reset flow.

# project/urls.py
from django.urls import path, include

urlpatterns = [
    # Adds /accounts/login/, /accounts/logout/,
    # /accounts/password_reset/, and more — for free.
    path("accounts/", include("django.contrib.auth.urls")),
]

# LoginView looks for a template at registration/login.html
# settings.py
LOGIN_REDIRECT_URL = "/dashboard/"
LOGOUT_REDIRECT_URL = "/"

For sign-up, the UserCreationForm gives you a complete, validated registration form. Render it in a view, and on a valid POST call form.save() to create the account.

from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def signup(request):
    if request.method == "POST":
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()              # creates the user, hashes the password
            return redirect("login")
    else:
        form = UserCreationForm()
    return render(request, "signup.html", {"form": form})

In a template you can show different links depending on whether the visitor is signed in:

{% if user.is_authenticated %}
    <p>Hello, {{ user.username }}!</p>
    <a href="{% url 'logout' %}">Log out</a>
{% else %}
    <a href="{% url 'login' %}">Log in</a>
{% endif %}
# Worked example: a stand-in for UserCreationForm validation.
# A real form checks the two password fields match and the
# username is free, then saves a hashed account.

import hashlib

existing_usernames = {"alice"}

def fake_hash(raw):
    return "pbkdf2_sha256$" + hashlib.sha256(raw.encode()).hexdigest()

def user_creation_form(data):
    errors = []
    if data["username"] in existing_usernames:
        errors.append("That username is taken.")
    if data["password1"] != data["password2"]:
        errors.append("The two password fields didn't match.")
    if errors:
        return {"valid": False, "errors": errors}
    # save(): create the account with a HASHED password
    account = {"username": data["username"], "password": fake_hash(data["password1"])}
    existing_usernames.add(data["username"])
    return {"valid": True, "account": account}

ok = user_creation_form({"username": "bob", "password1": "pw12345", "password2": "pw12345"})
print("valid:", ok["valid"], "| stored hash:", ok["account"]["password"][:14], "...")

bad = user_creation_form({"username": "carol", "password1": "abc", "password2": "xyz"})
print("valid:", bad["valid"], "| errors:", bad["errors"])

# ✅ Expected output:
# valid: True | stored hash: pbkdf2_sha256$ ...
# valid: False | errors: ["The two password fields didn't match."]

🎯 Your Turn: Finish the Login Flow

Fill in the blanks so the view verifies the credentials and then starts the session. One blank should be authenticate and one should be login.

# 🎯 YOUR TURN: replace each ___ with the right function name.
# Hint: first verify the credentials, then start the session.

def fake_authenticate(username, password):
    return "alice" if (username == "alice" and password == "s3cret-pass") else None

session = {}

def do_login(username, password):
    # Step 1: verify -> should call ___  (returns the user or None)
    user = ___(username, password)
    if user is not None:
        # Step 2: start the session -> should call ___
        ___(session, user)
        return f"Logged in as {user}"
    return "Invalid login"

# Wire your blanks to these helpers:
#   verify step  -> fake_authenticate
#   session step -> lambda s, u: s.__setitem__("user", u)
print(do_login("alice", "s3cret-pass"))
print(do_login("alice", "wrong"))

# EXPECTED OUTPUT:
# Logged in as alice
# Invalid login

Common Errors (And How to Fix Them)

❌ Storing the raw password instead of using create_user()

Setting user.password = "plain" stores an unusable, insecure string — login will never succeed and real passwords are exposed.

✅ Fix: use User.objects.create_user(...) or user.set_password("...") so the password is hashed.

❌ authenticate() returns None for bad credentials

A wrong username or password makes authenticate() return None, and passing None to login() raises an error.

✅ Fix: always check if user is not None: before calling login().

❌ Forgetting to call login() after authenticate()

authenticate() only verifies — it does not start a session. Skip login() and the visitor is never actually signed in.

✅ Fix: after a successful authenticate(), call login(request, user) to begin the session.

📋 Quick Reference

Function / AttributeWhat It Does
authenticate(request, username, password)Verify credentials; returns the User or None
login(request, user)Start a session for an authenticated user
logout(request)End the current user's session
request.user.is_authenticatedTrue if the visitor is signed in
User.objects.create_user(...)Create a user with a hashed password

🏆 Mini Challenge: A Tiny Auth System

Build a miniature auth system: register a user with a hashed password, then log them in only when the credentials match.

# 🏆 Mini Challenge: register, then authenticate + login.

import hashlib

users = {}
session = {}

def hash_pw(raw):
    return "pbkdf2_sha256$" + hashlib.sha256(raw.encode()).hexdigest()

def register(username, password):
    # Always store the HASH, never the raw password.
    users[username] = hash_pw(password)

def authenticate(username, password):
    return username if users.get(username) == hash_pw(password) else None

def login(username):
    if username is not None:
        session["user"] = username

# 1. Register
register("dana", "open-sesame")

# 2. Try a wrong password
login(authenticate("dana", "nope"))
print("after wrong password, logged in:", "user" in session)

# 3. Try the right password
login(authenticate("dana", "open-sesame"))
print("after correct password, logged in:", "user" in session)
print("current user:", session.get("user"))

# EXPECTED:
# after wrong password, logged in: False
# after correct password, logged in: True
# current user: dana

❓ Frequently Asked Questions

Lesson complete — you can sign users in and out!

You now know how the User model stores hashed passwords, how to create accounts with create_user(), how authenticate() and login() work together, how to sign out with logout(), how to check request.user.is_authenticated, and how Django's built-in auth views and UserCreationForm save you from writing it all yourself.

Practice quiz

What does authenticate() return on success?

  • True
  • The matching User object
  • A session id
  • None

Answer: The matching User object. authenticate() returns the User if the credentials match, or None if they do not.

What does login() do after authenticate()?

  • Hashes the password
  • Returns the User
  • Starts a session for the authenticated user
  • Creates the account

Answer: Starts a session for the authenticated user. login(request, user) starts a session so the user stays signed in across requests.

Which is the safe way to create a user with a hashed password?

  • User.objects.create_user(...)
  • User(password='plain').save()
  • user.password = 'plain'
  • User.objects.raw_create(...)

Answer: User.objects.create_user(...). create_user() hashes the password before saving; never assign the password field directly.

How do you check whether the current visitor is signed in?

  • request.is_logged_in
  • request.session.active
  • request.has_user
  • request.user.is_authenticated

Answer: request.user.is_authenticated. request.user.is_authenticated is True for signed-in users and False for AnonymousUser.

Does Django store raw passwords?

  • Yes, in plain text
  • No, it stores a salted hash (PBKDF2 by default)
  • Only the first 8 characters
  • Yes, but encrypted reversibly

Answer: No, it stores a salted hash (PBKDF2 by default). Django stores only a salted hash like pbkdf2_sha256$..., never the raw password.

What ends the current user's session?

  • logout(request)
  • request.user.delete()
  • authenticate(None)
  • login(request, None)

Answer: logout(request). logout(request) clears the session and signs the user out.

Including which URLconf wires up LoginView, LogoutView, and password reset?

  • django.contrib.admin.urls
  • django.urls.auth
  • django.contrib.auth.urls
  • django.auth.views

Answer: django.contrib.auth.urls. include('django.contrib.auth.urls') provides the ready-made auth views.

Which form gives you a complete, validated registration form?

  • LoginForm
  • AuthForm
  • SignupModel
  • UserCreationForm

Answer: UserCreationForm. UserCreationForm validates the data and saves a new user with a hashed password.

What is request.user for an anonymous visitor?

  • None
  • An AnonymousUser instance
  • An empty string
  • A 403 error

Answer: An AnonymousUser instance. Anonymous visitors get an AnonymousUser, whose is_authenticated is False.

What happens if you pass None to login()?

  • It raises an error
  • It logs in as admin
  • It silently succeeds
  • It returns False

Answer: It raises an error. Always check 'if user is not None' before calling login(); passing None raises an error.

Continue this course

Frequently asked questions

What is the difference between authenticate() and login() in Django?

authenticate() checks a username and password against the database and returns the matching User object if the credentials are correct, or None if they are not. It does not create a session. login() takes that returned User and actually starts a logged-in session by attaching the user's id to the request's session, so the user stays signed in across future requests. You almost always call authenticate() first, then pass its result to login().

Does Django store passwords in plain text?

No. Django never stores raw passwords. When you call User.objects.create_user() or set_password(), Django hashes the password using a strong, salted algorithm (PBKDF2 by default) and stores only the hash. When someone logs in, Django hashes the submitted password the same way and compares hashes. This is why you must use create_user() or set_password() rather than assigning to the password field directly — assigning directly would store an unusable plain string.

How do I check whether the current visitor is logged in?

Every request object carries request.user. If the visitor is signed in, request.user is their User object and request.user.is_authenticated is True. If they are not signed in, request.user is an AnonymousUser and request.user.is_authenticated is False. You can branch on request.user.is_authenticated in a view, or use the @login_required decorator to block anonymous visitors entirely.

Do I have to build my own login pages?

No. Django ships ready-made auth views. Including django.contrib.auth.urls in your URLconf wires up LoginView, LogoutView, and the password-reset flow for free. You only need to provide a template named registration/login.html. For sign-up, Django's UserCreationForm gives you a complete, validated registration form out of the box, so you can build a working account system with very little code.