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 Admincreatesuperuser, is_staff
  • Migrations applied (auth tables exist after first migrate)

Auth Components

PieceRole
User modelUsername, email, password hash, flags
SessionKeeps user logged in across requests
Authentication middlewareSets request.user
PermissionsPer-model add/change/delete/view
GroupsBundle 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:

python
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):

python
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:

bash
python manage.py runserver

Visit 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:

python
from django.contrib.auth.forms import UserCreationForm
 
 
class SignUpForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        fields = ["username", "email"]

blog/views.py:

blog/urls.py:

python
path("accounts/signup/", views.signup, name="signup"),

UserCreationForm validates password strength and confirmation.

Protect Views

FBV decorator

python
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

python
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
 
class PostCreateView(LoginRequiredMixin, CreateView):
    ...
    login_url = "login"

Object ownership test

python
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
 
    def test_func(self):
        return self.get_object().author == self.request.user

Failure returns 403 by default.

Template Auth Checks

django
{% 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:

text
PBKDF2 (default) → stored in User.password field

Verify in shell:

python
from django.contrib.auth.models import User
u = User.objects.get(username="admin")
u.check_password("your-password")  # True/False

Use create_user() / UserCreationForm, not raw password assignment.

Sessions

Default engine: database (django_session table).

Alternatives in settings.py:

python
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 = False

Login creates session; logout flushes it.

Permissions

Assign in admin

Groups → Editors with blog | post | Can change post, etc.

Check in views

python
from django.contrib.auth.decorators import permission_required
 
@permission_required("blog.change_post", raise_exception=True)
def post_edit(request, pk):
    ...

CBV:

python
from django.contrib.auth.mixins import PermissionRequiredMixin
 
class PostAdminView(PermissionRequiredMixin, UpdateView):
    permission_required = "blog.change_post"

Check in templates

django
{% 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

FlagMeaning
is_activeCan log in
is_staffCan access /admin/ login
is_superuserAll permissions bypass

Regular site users: is_active=True, is_staff=False.

From chapter 10:

python
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

On create:

python
post.author = request.user

Filter “my posts”:

python
Post.objects.filter(author=request.user)

Compare with Spring Security

DjangoSpring Boot
@login_required@PreAuthorize, SecurityFilterChain
Session cookieHttpSession / JWT (custom)
User modelUserDetails

See Spring Security and JWT for Java-side patterns.

Security Checklist

  • SECRET_KEY secret; DEBUG=False in 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

MistakeSymptomFix
No LOGIN_URL404 redirect to /accounts/login/Set LOGIN_URL = "login"
Logout GET linkCSRF/logout issues in Django 5+POST form to logout
login() without backendRare errorsUse django.contrib.auth.login
Permission string typo403 alwaysCheck codename in admin
Custom user mid-projectMigration painSet 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.