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) orLoginRequiredMixin(classes) redirect anonymous users toLOGIN_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.