Skip to content

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.

python
'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).

python
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).

python
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)
python
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.

python
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 StatePrimary Auth SchemeHTTP Status
AuthenticatedAny403 Forbidden
UnauthenticatedUses WWW-Authenticate (e.g., Tokens, Basic)401 Unauthorized
UnauthenticatedNo 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:

  1. 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.
  2. Object Creation: Because .get_object() is only called when retrieving or updating existing objects, object-level checks are not run during POST creation 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.

python
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 obj

Built-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:

  • POST requests require the add permission.
  • PUT and PATCH requests require the change permission.
  • DELETE requests require the delete permission.

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.

python
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.user

Overview of Access Restrictions

DRF provides three primary layers to customize access restrictions. They apply in different scenarios and have different limitations:

  1. 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.
  2. 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).
  3. serializer_class: Enforces instance-level restrictions on input validation and output serialization. Serializers have access to the request context to make dynamic decisions.

Restriction Matrix

The following table breaks down the level of control each layer offers over specific actions:

Actionquerysetpermission_classesserializer_class
listglobalglobalobject-level*
createnoglobalobject-level
retrieveglobalobject-levelobject-level
updateglobalobject-levelobject-level
partial_updateglobalobject-levelobject-level
destroyglobalobject-levelno
Access to action?no**yesno**
Access to request?no**yesyes

* 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.