User Authentication and Permissions
Introduction
Django’s built-in django.contrib.auth app provides user accounts, password hashing, sessions, groups, and permissions—integrated with admin, forms, and templates. This chapter wires login, logout, and registration, protects views with @login_required, checks permissions, and covers session security settings for production.
Prerequisites
- Forms and CSRF — POST forms,
PostForm - Django Admin —
createsuperuser,is_staff - Migrations applied (auth tables exist after first
migrate)
Auth Components
| Piece | Role |
|---|---|
User model | Username, email, password hash, flags |
| Session | Keeps user logged in across requests |
| Authentication middleware | Sets request.user |
| Permissions | Per-model add/change/delete/view |
| Groups | Bundle permissions for roles |
Default user model: django.contrib.auth.models.User. Custom user models — plan before first migrate (chapter 10 preview).
Login and Logout with Built-in Views
config/urls.py:
from django.contrib.auth import views as auth_views
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
path("", include("blog.urls")),
]settings.py (or base.py):
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"Login template
templates/registration/login.html (project-level templates/):
Ensure TEMPLATES["DIRS"] includes BASE_DIR / "templates".
Test:
python manage.py runserverVisit http://127.0.0.1:8000/accounts/login/ with superuser credentials.
Registration View (Simple)
Django does not ship a public signup view—add one:
blog/forms.py:
from django.contrib.auth.forms import UserCreationForm
class SignUpForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
fields = ["username", "email"]blog/views.py:
blog/urls.py:
path("accounts/signup/", views.signup, name="signup"),UserCreationForm validates password strength and confirmation.
Protect Views
FBV decorator
from django.contrib.auth.decorators import login_required
@login_required
def post_create(request):
...Optional login URL override: @login_required(login_url="/accounts/login/")
CBV mixin
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class PostCreateView(LoginRequiredMixin, CreateView):
...
login_url = "login"Object ownership test
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
def test_func(self):
return self.get_object().author == self.request.userFailure returns 403 by default.
Template Auth Checks
{% if user.is_authenticated %}
<p>Hello, {{ user.username }}</p>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">Log out</button>
</form>
{% else %}
<a href="{% url 'login' %}">Log in</a>
<a href="{% url 'signup' %}">Sign up</a>
{% endif %}user comes from auth context processor—no extra view code.
Password Storage
Django never stores plaintext passwords. On set_password() / user creation:
PBKDF2 (default) → stored in User.password fieldVerify in shell:
from django.contrib.auth.models import User
u = User.objects.get(username="admin")
u.check_password("your-password") # True/FalseUse create_user() / UserCreationForm, not raw password assignment.
Sessions
Default engine: database (django_session table).
Alternatives in settings.py:
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_COOKIE_AGE = 1209600 # 2 weeks
SESSION_COOKIE_SECURE = True # HTTPS only (production)
SESSION_COOKIE_HTTPONLY = True # default; JS cannot read session cookie
SESSION_SAVE_EVERY_REQUEST = FalseLogin creates session; logout flushes it.
Permissions
Assign in admin
Groups → Editors with blog | post | Can change post, etc.
Check in views
from django.contrib.auth.decorators import permission_required
@permission_required("blog.change_post", raise_exception=True)
def post_edit(request, pk):
...CBV:
from django.contrib.auth.mixins import PermissionRequiredMixin
class PostAdminView(PermissionRequiredMixin, UpdateView):
permission_required = "blog.change_post"Check in templates
{% if perms.blog.change_post %}
<a href="{% url 'post-edit' pk=post.pk %}">Edit</a>
{% endif %}Permission codenames: <app_label>.<action>_<model> — e.g. blog.delete_post.
Staff vs Superuser vs Active
| Flag | Meaning |
|---|---|
is_active | Can log in |
is_staff | Can access /admin/ login |
is_superuser | All permissions bypass |
Regular site users: is_active=True, is_staff=False.
Link Posts to Users
From chapter 10:
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)On create:
post.author = request.userFilter “my posts”:
Post.objects.filter(author=request.user)Compare with Spring Security
| Django | Spring Boot |
|---|---|
@login_required | @PreAuthorize, SecurityFilterChain |
| Session cookie | HttpSession / JWT (custom) |
User model | UserDetails |
See Spring Security and JWT for Java-side patterns.
Security Checklist
-
SECRET_KEYsecret;DEBUG=Falsein production - HTTPS;
SESSION_COOKIE_SECURE,CSRF_COOKIE_SECURE - Strong password validators in
AUTH_PASSWORD_VALIDATORS(default enabled) - Rate-limit login (django-axes or reverse proxy)—optional
- Do not expose user enumeration in signup (“username taken” is acceptable; email reset timing matters)
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
No LOGIN_URL | 404 redirect to /accounts/login/ | Set LOGIN_URL = "login" |
| Logout GET link | CSRF/logout issues in Django 5+ | POST form to logout |
login() without backend | Rare errors | Use django.contrib.auth.login |
| Permission string typo | 403 always | Check codename in admin |
| Custom user mid-project | Migration pain | Set AUTH_USER_MODEL early |
Post-Chapter Checklist
- Login/logout URLs work with templates
- Signup creates user and session (or redirects to login)
- Create/edit post requires authentication
- Templates show
user.is_authenticated - You understand permissions vs superuser
FAQ## FAQ
Email-based login?
Custom authentication backend matching email instead of username—or use django-allauth.
“Remember me”?
Extend SESSION_COOKIE_AGE or set session expiry on login.
Logout via GET?
Django 5 LogoutView expects POST—use form button in nav.
JWT instead of sessions?
Use DRF SimpleJWT for APIs; browser apps may still use sessions.
Reset forgotten password?
Built-in PasswordResetView — email backend required; configure EMAIL_* settings.