Custom Permissions & Authentication

Build object-level permission checks, custom auth backends, and role-based access that scales.

Django's auth system has three layers people conflate: authentication (who are you?), permissions (what may you do?), and object-level checks (may you do it to this row?). Django gives you the first two out of the box; the third you build yourself.

Model permissions and the mixins

Every model gets four auto-permissions (add, change, delete, view). Add custom ones in Meta.permissions. In views, gate access with PermissionRequiredMixin; for a runtime rule, use UserPassesTestMixin and implement test_func().

from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic import UpdateView

class ArticleEdit(UserPassesTestMixin, UpdateView):
    model = Article
    fields = ['title', 'body']

    def test_func(self):
        # Object-level: only the author may edit.
        return self.get_object().author == self.request.user

Custom authentication backends

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

Example

Example · python
# backends.py — authenticate by email (case-insensitive), safe against timing attacks.
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password

User = get_user_model()


class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        email = username or kwargs.get('email')
        if email is None or password is None:
            return None
        try:
            user = User.objects.get(email__iexact=email)
        except User.DoesNotExist:
            # Run the hasher anyway so timing doesn't reveal which emails exist.
            User().set_password(password)
            return None
        if user.check_password(password) and self.user_can_authenticate(user):
            return user
        return None


# settings.py
# AUTHENTICATION_BACKENDS = [
#     'accounts.backends.EmailBackend',
#     'django.contrib.auth.backends.ModelBackend',  # keep as fallback
# ]


# A reusable DRF-style object permission expressed as a predicate.
from rest_framework.permissions import BasePermission, SAFE_METHODS


class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True                     # anyone may read
        return obj.owner_id == request.user.id   # only the owner may write

When to use it

  • A developer writes a custom DRF permission class to allow read access to everyone but restrict writes to owners.
  • A team uses object-level permissions with django-guardian to let each user manage only their own resources.
  • A developer overrides has_permission and has_object_permission in a DRF permission class for two-stage access control.

More examples

Custom DRF permission class

Allows safe (GET/HEAD/OPTIONS) requests to all; restricts mutations to the object's author.

Example · python
from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return obj.author == request.user

Apply permission to a ViewSet

Stacks IsAuthenticated and the custom IsOwnerOrReadOnly permission on a ViewSet.

Example · python
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

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

has_permission vs has_object_permission

Shows that has_permission gates list/create actions while has_object_permission gates retrieve/update/delete.

Example · python
class StrictPermission(BasePermission):
    def has_permission(self, request, view):
        # Called for every request; check user is active
        return request.user and request.user.is_active

    def has_object_permission(self, request, view, obj):
        # Called for single-object views; check ownership
        return obj.owner == request.user

Discussion

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