Yesterday I built forms that let users submit data. Today, for day 69, it was Authentication in Django. Django ships with a complete authentication system out of the box. User registration, login, logout, password handling, and protecting pages from unauthenticated access. All of it, without installing a single extra package.
What Django Gives You for Free
Django's django.contrib.auth app is included by default in INSTALLED_APPS. It provides:
- A built-in
Usermodel - Login and logout views
- Password hashing
- Session management
- Permission and group system
- Decorators to protect views
You don't build any of this from scratch.
The Built-in User Model
Django's User model lives in django.contrib.auth.models. It comes with these fields by default:
username-
password— stored as a hash, never plain text email-
first_name,last_name is_active-
is_staff— can access admin panel -
is_superuser— has all permissions date_joined-
last_login
from django.contrib.auth.models import User
# create a user
user = User.objects.create_user(username='haris', password='securepass123')
# get a user
user = User.objects.get(username='haris')
print(user.email)
print(user.is_authenticated) # True
Always use create_user() instead of create(); it handles password hashing automatically.
User Registration
Django doesn't provide a registration view out of the box, but it does provide UserCreationForm: a built-in form for creating new users.
# core/forms.py
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegisterForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
UserCreationForm already handles password confirmation and validation. We extend it here just to add an email field.
# core/views.py
from django.shortcuts import render, redirect
from .forms import RegisterForm
def register(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
form.save()
return redirect('login')
else:
form = RegisterForm()
return render(request, 'core/register.html', {'form': form})
<!-- core/templates/core/register.html -->
{% extends 'core/base.html' %}
{% block content %}
<h1>Register</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create Account</button>
</form>
<p>Already have an account? <a href="{% url 'login' %}">Login</a></p>
{% endblock %}
Login and Logout
Django provides built-in views for login and logout. You just wire them to URLs.
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('core.urls')),
path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
You provide your own template for login, Django handles all the logic.
<!-- core/templates/core/login.html -->
{% extends 'core/base.html' %}
{% block content %}
<h1>Login</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
<p>Don't have an account? <a href="{% url 'register' %}">Register</a></p>
{% endblock %}
After a successful login, Django redirects to /accounts/profile/ by default. Override this in settings.py:
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'login'
Accessing the Current User in Views
Once a user is logged in, Django attaches them to every request as request.user.
def home(request):
print(request.user) # the User object
print(request.user.username) # 'haris'
print(request.user.is_authenticated) # True if logged in, False if not
An unauthenticated user is represented by AnonymousUser. Always check is_authenticated before accessing user data.
Accessing the Current User in Templates
Django automatically makes request.user available in every template through the request context processor, which is included by default.
{% if request.user.is_authenticated %}
<p>Welcome, {{ request.user.username }}</p>
<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'register' %}">Register</a>
{% endif %}
Put this in your base.html so it appears on every page.
Protecting Views
The @login_required Decorator
Use this on any view that requires authentication:
from django.contrib.auth.decorators import login_required
@login_required
def create_post(request):
...
If an unauthenticated user tries to access this view, Django redirects them to the login page automatically. Set where Django sends them in settings.py:
LOGIN_URL = 'login'
Checking in the View Manually
Sometimes you need more control:
def some_view(request):
if not request.user.is_authenticated:
return redirect('login')
...
Linking Posts to Users
In most apps, content belongs to a user. Add a ForeignKey to the User model on your Post:
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
on_delete=models.CASCADE means if the user is deleted, their posts are deleted too.
When saving a post from a form, attach the current user before saving:
@login_required
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('home')
else:
form = PostForm()
return render(request, 'core/create_post.html', {'form': form})
commit=False tells Django to create the object in memory but not save to the database yet, giving you a chance to set extra fields like author before the actual save.
Restricting Actions to the Owner
You don't want users editing each other's posts:
@login_required
def edit_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if post.author != request.user:
return redirect('home')
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect('home')
else:
form = PostForm(instance=post)
return render(request, 'core/edit_post.html', {'form': form})
URL Setup
# core/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('register/', views.register, name='register'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
path('post/create/', views.create_post, name='create_post'),
path('post/<int:pk>/edit/', views.edit_post, name='edit_post'),
]
The Full Authentication Flow
User visits /post/create/
↓
@login_required checks request.user.is_authenticated
↓
Not authenticated → redirect to /login/
↓
User fills in login form → POST to /login/
↓
Django validates credentials, creates session
↓
Redirect to LOGIN_REDIRECT_URL → /
↓
User visits /post/create/ again
↓
Now authenticated → view runs normally
Wrapping Up
Django's authentication system is one of the best reasons to use it. Registration, login, logout, session management, password hashing, all handled. Today the app became a real multi-user system where posts belong to users, protected pages require login, and only owners can edit their own content.
Thanks for reading. Feel free to share your thoughts!
Top comments (0)