Custom Permissions & Authentication

Go beyond login_required: object-level permissions, custom permission classes, and swapping in your own authentication backend.

Django ships two layers of access control, and knowing which to use where is a senior skill.

The built-in permission system

Every model automatically gets add, change, delete and view permissions. Check them with user.has_perm("blog.change_article"), or declare your own domain permissions in Meta.permissions (e.g. can_publish). These are model-level — they say "this user may edit articles", not "this article".

Object-level checks

"Can this user edit this specific article?" is object-level and Django's default backend does not answer it — you enforce it yourself, typically in get_queryset() (so users only ever see their own rows) or with UserPassesTestMixin. This is the more common real-world need.

Custom authentication backends

Need to log people in by email instead of username, or authenticate against an SSO / API token? Write an authentication backend — a class with authenticate() and get_user() — and add it to AUTHENTICATION_BACKENDS. Django tries each backend in order.

Whichever mechanism you choose, enforce access on the queryset, not just by hiding a button in the template. A hidden link is UX; a filtered queryset is security. If a user cannot see a row, they cannot POST to edit it either.

Example

Example · python
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic import UpdateView
from .models import Article

User = get_user_model()


# --- Custom model permission, declared on the model's Meta ---
# class Article(models.Model):
#     class Meta:
#         permissions = [("can_publish", "Can publish articles")]
# ...then check: if request.user.has_perm("blog.can_publish"): ...


# --- Object-level: a user may only edit an article they authored ---
class ArticleEdit(UserPassesTestMixin, UpdateView):
    model = Article
    fields = ["title", "body"]

    def test_func(self):
        return self.get_object().author == self.request.user

    # Even stronger: never let a foreign object be fetched at all -> 404, not 403.
    def get_queryset(self):
        return super().get_queryset().filter(author=self.request.user)


# --- Custom authentication backend: log in by email + password ---
class EmailBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(email__iexact=username)
        except User.DoesNotExist:
            return None
        return user if user.check_password(password) else None

    def get_user(self, user_id):
        return User.objects.filter(pk=user_id).first()

# settings.py:
# AUTHENTICATION_BACKENDS = [
#     "accounts.backends.EmailBackend",
#     "django.contrib.auth.backends.ModelBackend",
# ]

When to use it

  • A developer uses django-guardian to grant per-object permissions so each user only edits their own documents.
  • A team creates a custom DRF IsAdminOrOwner permission class to give admins full access and owners limited access.
  • A developer checks object-level permission in a DRF ViewSet using get_object() which calls check_object_permissions.

More examples

DRF IsAdminOrReadOnly permission

Allows GET/HEAD/OPTIONS to everyone but restricts data mutation to staff users.

Example · python
from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAdminOrReadOnly(BasePermission):
    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user and request.user.is_staff

Object-level permission in ViewSet

Explicitly calls check_object_permissions after fetching the object to enforce per-object access rules.

Example · python
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

    def get_object(self):
        obj = super().get_object()
        self.check_object_permissions(self.request, obj)
        return obj

Group-based permission check

Restricts access to users who belong to the 'Editors' group, checked against the auth_group table.

Example · python
from rest_framework.permissions import BasePermission

class IsEditor(BasePermission):
    def has_permission(self, request, view):
        return (
            request.user.is_authenticated
            and request.user.groups.filter(name="Editors").exists()
        )

Discussion

  • Be the first to comment on this lesson.
Custom Permissions & Authentication — Django | SoundsCode