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.userCustom 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
# 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 writeWhen 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.
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.userApply permission to a ViewSet
Stacks IsAuthenticated and the custom IsOwnerOrReadOnly permission on a ViewSet.
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.
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