Permissions
Permissions dictate whether an authenticated (or unauthenticated) user has the right to perform the requested action. They are checked before any logic in your view is run.
To determine if the incoming request should be permitted, permission checks will typically use the authentication information in the request.user and request.auth properties.
Authentication vs Permissions
Authentication by itself won't allow or disallow an incoming request; it simply identifies the user making the request. Permissions are what actually decide if that specific user is allowed to proceed.
Setting the Permission Policy
Like authentication, you can set permissions globally or per-view.
Default Behavior
By default, if you don't configure anything, DRF uses AllowAny, which means unrestricted access for everyone.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]You can override this globally in your settings.py. This keeps your code DRY by applying the same logic across all views (e.g., making your entire API require authentication by default).
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}Or you can configure it on a per-view basis.
NOTE
When you set new permission classes via the class attribute or decorators, you are telling the view to ignore the default list set in your settings.py file (They are not merged together).
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
content = {'status': 'request was permitted'}
return Response(content)from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
content = {'status': 'request was permitted'}
return Response(content)Composing Permissions
You can compose permissions using standard Python bitwise operators. This allows you to build complex logic directly in your view without needing to write a brand new permission class.
DRF supports the & (and), | (or), and ~ (not) operators, and allows the use of parentheses () to group expressions.
from rest_framework.permissions import IsAuthenticated, BasePermission
from rest_framework.views import APIView
class ReadOnly(BasePermission):
def has_permission(self, request, view):
return request.method in ('GET', 'HEAD', 'OPTIONS')
class ExampleView(APIView):
# Allow access if the user is authenticated OR if it's a read-only request
permission_classes = [IsAuthenticated | ReadOnly]How Permissions are Determined
Permission classes are evaluated in order. DRF checks each permission in the list before running the main body of the view.
If any permission check fails, the view will not run. DRF immediately raises an exceptions.PermissionDenied or exceptions.NotAuthenticated exception.
Unauthorized Responses
When a permission check fails, the error response depends entirely on the user's state and the highest priority authentication class in use:
| Request State | Primary Auth Scheme | HTTP Status |
|---|---|---|
| Authenticated | Any | 403 Forbidden |
| Unauthenticated | Uses WWW-Authenticate (e.g., Tokens, Basic) | 401 Unauthorized |
| Unauthenticated | No WWW-Authenticate (e.g., Sessions) | 403 Forbidden |
Object-Level Permissions
View-level permissions protect the entire endpoint. Object-level permissions protect specific rows in your database (model instances).
Although DRF's generic views automatically trigger object-level checks, most built-in permissions (like IsAuthenticated) inherit a default has_object_permission() method that simply returns True. Because of this, with the single exception of DjangoObjectPermissions, built-in classes do not actually restrict object access. To actually restrict object access, you must write a Custom Permission class (shown below).
Limitations of Object-Level Permissions
Object-level permissions have two major caveats you must be aware of:
- List Views (Performance): For performance reasons, generic views do not automatically apply object-level permissions to each instance when returning a list of objects. If you need this, you must explicitly filter the queryset to ensure users only see instances they are permitted to view.
- Object Creation: Because
.get_object()is only called when retrieving or updating existing objects, object-level checks are not run duringPOSTcreation requests. To restrict creation, you must implement the check in your Serializer or by overriding.perform_create().
Enforcing in Custom Views
If you are writing your own views (e.g., subclassing APIView), or if you override the .get_object() method on a generic view, you must explicitly call the .check_object_permissions(request, obj) method at the exact point you retrieve the object.
This method will either raise the appropriate PermissionDenied or NotAuthenticated exception, or simply return silently if the view has the appropriate permissions.
from django.shortcuts import get_object_or_404
def get_object(self):
obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
self.check_object_permissions(self.request, obj)
return objBuilt-in Permissions
DRF provides several built-in permissions out of the box.
AllowAny
The AllowAny permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated. While not strictly required (an empty list [] does the same thing), using this class makes your intention explicit.
IsAuthenticated
The IsAuthenticated permission class denies permission to any unauthenticated user, and allows permission otherwise. Suitable if you want your API to only be accessible to registered users.
IsAdminUser
The IsAdminUser permission class denies permission to any user, unless user.is_staff is True. Suitable if you want your API to only be accessible to a subset of trusted administrators.
IsAuthenticatedOrReadOnly
The IsAuthenticatedOrReadOnly class allows authenticated users to perform any request. Unauthenticated users are only permitted if the request method is one of the "safe" HTTP methods (GET, HEAD, OPTIONS).
DjangoModelPermissions
The DjangoModelPermissions class ties into Django's standard django.contrib.auth model permissions. It must only be applied to views that have a .queryset property or .get_queryset() method.
Authorization is granted if the user is authenticated and has the relevant model permissions:
POSTrequests require theaddpermission.PUTandPATCHrequests require thechangepermission.DELETErequests require thedeletepermission.
You can customize this behavior by subclassing DjangoModelPermissions and overriding the .perms_map property.
DjangoModelPermissionsOrAnonReadOnly
The DjangoModelPermissionsOrAnonReadOnly class is identical to DjangoModelPermissions, but it allows unauthenticated users to have read-only access to the API (GET, HEAD, OPTIONS).
DjangoObjectPermissions
The DjangoObjectPermissions class ties into Django's standard object permissions framework that allows per-object permissions on models.
Like DjangoModelPermissions, it must only be applied to views that have a .queryset property or .get_queryset() method.
Authorization is granted if the user is authenticated and has the relevant per-object permissions (add, change, delete). You can customize this behavior by subclassing DjangoObjectPermissions and overriding the .perms_map property.
Object-Level Backends
DjangoObjectPermissions requires a permission backend that supports object-level permissions. While django-guardian is the most popular choice, any valid object-level backend is supported equally well.
Custom Permissions
To implement a custom permission, override BasePermission and implement either, or both, of the following methods:
.has_permission(self, request, view): Checks if the user has access to the view in general..has_object_permission(self, request, view, obj): Checks if the user has access to a specific object (e.g., when retrieving or updating a specific instance).
The methods should return True if the request should be granted access, and False otherwise.
WARNING
Execution Order: Object-level permissions are only run if the view-level .has_permission() check has already passed.
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission that only allows managers to access the view,
and only allows the owner of a specific object to edit it.
"""
def has_permission(self, request, view):
# View-Level: Must be a logged-in manager to even access the endpoint
return bool(
request.user and
request.user.is_authenticated and
getattr(request.user, 'role', None) == 'manager'
)
def has_object_permission(self, request, view, obj):
# Object-Level: Any manager can read the object
if request.method in permissions.SAFE_METHODS:
return True
# Object-Level: Only the actual owner can edit/delete the object
return obj.owner == request.userOverview of Access Restrictions
DRF provides three primary layers to customize access restrictions. They apply in different scenarios and have different limitations:
get_queryset(): Limits the general visibility of objects. This strictly filters which objects are returned in lists, and determines which objects can even be targeted for modification/deletion.permission_classes: Evaluates permissions based on the request, action, and target object. (Note: Object-level permissions only apply to retrieve, update, and destroy actions. List and create actions only run view-level checks).serializer_class: Enforces instance-level restrictions on input validation and output serialization. Serializers have access to therequestcontext to make dynamic decisions.
Restriction Matrix
The following table breaks down the level of control each layer offers over specific actions:
| Action | queryset | permission_classes | serializer_class |
|---|---|---|---|
list | global | global | object-level* |
create | no | global | object-level |
retrieve | global | object-level | object-level |
update | global | object-level | object-level |
partial_update | global | object-level | object-level |
destroy | global | object-level | no |
Access to action? | no** | yes | no** |
Access to request? | no** | yes | yes |
* Serializers should not raise PermissionDenied during a list action, as it would cause the entire list request to fail rather than just hiding the object.
** By overriding the .get_queryset() or .get_serializer_class() methods on your view, you can access self.request and self.action to apply different security rules dynamically.
TIP
If your application requires highly complex permission logic, check out the ecosystem of Third-Party Packages for DRF.
