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
request.user actually comes fromrequest.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 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.
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").
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.
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."
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?
Django