Skip to main content
All posts
DjangoAPISecurity

Building Reusable Permission Classes in Django REST Framework

How to move beyond scattered, ad-hoc permission checks and build clean, testable, composable permission classes that scale with your API.

Munal Poudel5 min read

Every API has access control logic. The question isn't whether you'll write permission checks — it's whether they'll be scattered across your view methods or consolidated into reusable, testable units. On BookMyTest (a test-voucher booking platform for IELTS, GRE, GMAT, and similar exams) I chose the latter, and it made the codebase dramatically easier to maintain.

The Problem with Ad-Hoc Permission Checks

When permission logic lives inside view methods — buried in if-statements — it's invisible to the framework, difficult to test in isolation, and easy to accidentally omit. Every new view becomes an opportunity to forget a check. As your API grows, this leads to inconsistent enforcement and security gaps that are hard to audit.

Django REST Framework's BasePermission

DRF gives you a clean hook: subclass BasePermission, implement has_permission (and optionally has_object_permission), and DRF calls it automatically before your view logic runs. The check is now explicit, named, and centrally defined.

python
from rest_framework.permissions import BasePermission

class IsLabAdmin(BasePermission):
    """Allows access only to users with the 'lab_admin' role."""

    message = "You must be a lab administrator to perform this action."

    def has_permission(self, request, view):
        return (
            request.user.is_authenticated
            and request.user.role == "lab_admin"
        )

class IsOwnerOrAdmin(BasePermission):
    """Object-level: allow owners to access their own records, or admins."""

    def has_object_permission(self, request, view, obj):
        if request.user.role == "lab_admin":
            return True
        return obj.user == request.user

Composing Permissions

DRF lets you stack multiple permission classes on a view — all must pass. This means you can compose fine-grained checks from small, single-responsibility classes rather than building one monolithic class that handles every scenario.

python
class BookingDetailView(RetrieveUpdateDestroyAPIView):
    # IsAuthenticated AND IsOwnerOrAdmin must both pass
    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]

Benefits in Practice

  • Each permission class has a single responsibility — easy to read, reason about, and test.
  • Permissions are tested independently of views, giving you high confidence in your access control logic.
  • Adding a new role or rule means writing a new class, not hunting through existing view code.
  • The permission_classes declaration on each view is a clear, auditable record of who can access what.