Role-Based Access Control (RBAC) in Django REST Framework — A Practical Pattern
Role-based access control (RBAC) means different types of users — admins, staff, regular users, etc. — get different levels of access to your API. The most common mistake is scattering `if user.role == 'admin'` checks across every view. Here's a cleaner pattern.
Step 1: Define roles clearly
Add a `role` field to your user model (or a related `Profile` model) with a fixed set of choices:
class User(AbstractUser):
class Role(models.TextChoices):
ADMIN = "ADMIN", "Admin"
STAFF = "STAFF", "Staff"role = models.CharField(max_length=20, choices=Role.choices, default=Role.CUSTOMER) ```
Step 2: Build reusable permission classes
Instead of checking roles inline in every view, create permission classes once and reuse them:
class IsAdmin(BasePermission): def has_permission(self, request, view): return request.user.is_authenticated and request.user.role == "ADMIN"
class IsStaffOrAdmin(BasePermission): def has_permission(self, request, view): return request.user.is_authenticated and request.user.role in ["STAFF", "ADMIN"] ```
Step 3: Apply them to views with one line
class ReportsView(APIView):
permission_classes = [IsStaffOrAdmin]Now every view simply declares which roles can access it — no repeated logic, and it's obvious at a glance what each endpoint requires.
Step 4: Handle object-level permissions separately
Role checks handle "can this type of user access this endpoint at all," but you'll often also need object-level checks — e.g. "can this customer see *this specific* order." Handle that inside the view or serializer, not the permission class, since it depends on the specific object being accessed.
Why this pattern scales well
As the app grows and you add new roles or new endpoints, you're combining existing permission classes rather than writing new conditional logic each time. It also makes the codebase easier for other developers to read — the permission requirement is visible right at the top of the view.
FAQ
Should I use Django's built-in Groups and Permissions instead of a custom role field? Django's Groups system works well for admin-panel-style permissions, but a custom `role` field is usually simpler and faster for API-driven RBAC, especially when roles map directly to product concepts like "customer" vs. "staff."
How do I test permission classes? Write unit tests that create users with each role and assert which endpoints return 200 vs. 403 for each — this catches permission regressions early, before they reach production.
Can a user have more than one role? The simple `CharField` pattern above assumes one role per user. If you need multiple roles per user, use a many-to-many relationship to a `Role` model instead, and update the permission classes to check `request.user.roles.filter(name="ADMIN").exists()`.
Author: Nayan Kalola
Python Backend Developer