Django's authentication and permission system
How request.user gets set on every request, and the actual difference between "are you logged in," "are you allowed to do this," and "which specific rows can you touch."
3 min read
Where request.user actually comes from
request.user isn't magic — it's set by AuthenticationMiddleware, which reads the session (attached earlier in the chain by SessionMiddleware) and looks up the corresponding user, attaching it to every request before your view runs:
MIDDLEWARE = [
# ...
"django.contrib.sessions.middleware.SessionMiddleware", # must come first
"django.contrib.auth.middleware.AuthenticationMiddleware", # reads the session
# ...
]If no one's logged in, request.user is an instance of AnonymousUser, not None — it deliberately implements the same interface as a real user (is_authenticated, has_perm(), etc.) so code can call those methods without a separate null check on every access. AnonymousUser.is_authenticated is always False; that's the reliable way to check login state, not request.user is None.
Authentication vs. authorization — two different questions
Authentication answers "who are you" — is this session tied to a real, logged-in user. Authorization answers "what are you allowed to do" — given that you're logged in as this user, can you actually perform this specific action. Django keeps these genuinely separate:
if not request.user.is_authenticated:
return redirect("login") # authentication check
if not request.user.has_perm("blog.delete_post"):
return HttpResponseForbidden() # authorization checkA user can be fully authenticated and still be authorized to do almost nothing — the two checks answer different questions and both usually need to happen, in that order.
Permissions: model-level, not row-level
Django's built-in permission system is per-model, not per-object — auth.Permission rows like blog.add_post, blog.change_post, blog.delete_post are created automatically for every model, and has_perm("blog.change_post") answers "can this user change some Post," not "can this user change this specific Post":
class BlogPost(models.Model):
class Meta:
permissions = [("publish_post", "Can publish a blog post")] # custom permissionCustom permissions are declared the same way, and checked identically: request.user.has_perm("blog.publish_post").
When per-object permission actually matters
If "can this user edit this specific comment" (as opposed to comments in general) is a real requirement, that's not what Django's built-in system checks at all — has_perm() has an optional obj argument specifically for this, but the default ModelBackend ignores it entirely and always returns False for object-level checks. This is exactly the gap third-party packages like django-guardian fill, or, just as commonly, a plain explicit check written directly in the view:
def edit_comment(request, comment_id):
comment = get_object_or_404(Comment, id=comment_id)
if comment.author_id != request.user.id:
return HttpResponseForbidden()
# ...For a lot of real apps, "is this the owner" is a one-line check that doesn't need a permissions framework at all — reaching for django-guardian before confirming the built-in permission system genuinely doesn't fit is easy to over-engineer.
Groups: permissions assigned in bulk
A Group is just a named bundle of permissions — instead of assigning blog.publish_post to fifty individual editor accounts, it's assigned once to an "Editors" group, and each editor is added to that group. user.has_perm(...) checks permissions from both the user directly and every group they belong to, so day-to-day permission management becomes "which group is this person in," not "which permissions does this specific account have."
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is request.user when nobody is logged in — is it None?
2. What's the actual difference between authentication and authorization?
3. Does request.user.has_perm('blog.change_post') check whether the user can edit one specific Post?
4. What's a common, simple way to implement 'is this user the owner of this specific comment,' given Django's permissions are model-level?