Authentication and permissions

Django · Practical · 14 min

Almost every app needs accounts, and Django's auth system is secure and complete out of the box.

django.contrib.auth provides users, password hashing, sessions-based login, permissions and groups.

Login and logout. Include the built-in views: path("accounts/", include("django.contrib.auth.urls")) gives login, logout, password change and password reset URLs. You supply templates such as registration/login.html. Settings LOGIN_URL, LOGIN_REDIRECT_URL and LOGOUT_REDIRECT_URL control redirects. Logging out requires a POST (a form with a CSRF token), not a link.

In views, request.user is the current user or an AnonymousUser; check request.user.is_authenticated. In templates, {{ user }} is available.

Protecting views:

  • @login_required (functions) or LoginRequiredMixin (classes) redirect anonymous users to LOGIN_URL?next=...
  • Django 5.1 added LoginRequiredMiddleware, which makes every view require login unless marked @login_not_required

Permissions. Each model gets add, change, delete and view permissions automatically, named like "blog.change_post". Check with user.has_perm("blog.change_post"), @permission_required(...), or PermissionRequiredMixin. Assign permissions to groups ("Editors") and users to groups. Superusers have all permissions. For per-object rules ("only the author may edit"), filter querysets or check ownership in the view.

Programmatic login: user = authenticate(request, username=..., password=...) then login(request, user). Passwords are stored hashed (PBKDF2 by default, Argon2 if configured); never store or compare raw passwords yourself.

Example

# mysite/urls.py
from django.urls import include, path
urlpatterns = [path("accounts/", include("django.contrib.auth.urls"))]   # login/, logout/, ...

# settings.py
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "blog:list"

# blog/views.py
from django.contrib.auth.decorators import login_required, permission_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, render

@login_required
def dashboard(request):
    return render(request, "blog/dashboard.html", {"mine": request.user.author.posts.all()})

@permission_required("blog.change_post", raise_exception=True)    # 403 if missing
def moderate(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if not (request.user.is_superuser or post.author.user == request.user):
        raise PermissionDenied
    ...
# templates/registration/login.html:  <form method="post">{% csrf_token %}{{ form }}<button>Log in</button></form>
# logout must be POST: <form method="post" action="{% url 'logout' %}">{% csrf_token %}<button>Log out</button></form>

Remember

  • include("django.contrib.auth.urls") gives login, logout and password views
  • @login_required / LoginRequiredMixin protect views
  • Permissions are app_label.action_model, assigned via groups
  • Logout is a POST request

Watch out for

  • Linking to logout with a plain <a href>; since Django 5.0 LogoutView only accepts POST.

Check yourself

  1. What is the permission codename Django creates for editing Post objects in the blog app?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.djangoproject.com/en/6.0/topics/auth/default/