Built-in Permission Classes in Django REST Framework

Last updated June 23rd, 2021

This article looks at how the built-in permission classes work in Django REST Framework (DRF).

--

Django REST Framework Permissions Series:

  1. Permissions in Django REST Framework
  2. Built-in Permission Classes in Django REST Framework (this article!)
  3. Custom Permission Classes in Django REST Framework

Contents

Objectives

By the end of this article, you should be able to:

  1. Explain the differences between the seven built-in permission classes in DRF
  2. Set permissions on a specific model and object
  3. Use the built-in permission classes to set a global permission policy

Built-in Classes

Although you can create your own permission classes, DRF comes with seven built-in classes intended to make your life easier:

  1. AllowAny
  2. IsAuthenticated
  3. IsAuthenticatedOrReadOnly
  4. IsAdminUser
  5. DjangoModelPermissions
  6. DjangoModelPermissionsOrAnonReadOnly
  7. DjangoObjectPermissions

Using them is as simple as including the class in the permission_classes list of a specific API View. They stretch from entirely open (AllowAny) to access granted only to admin users (IsAdminUser). With very little additional work, you can use them to implement fine-grained access control -- either on a model or at the object level. You can also set permissions globally, for all API endpoints.

All of those classes, except the last one, DjangoObjectPermissions, override just the has_permission method and inherits the has_object_permission from the BasePermission class. has_object_permission in the BasePermission class always returns True, so it has no impact on object-level access restriction:

Permission class has_permission has_object_permission
AllowAny
IsAuthenticated
IsAuthenticatedOrReadOnly
IsAdminUser
DjangoModelPermissions
DjangoModelPermissionsOrAnonReadOnly
DjangoObjectPermissions by extending DjangoModelPermissions

For more on has_permission vs has_object_permission, be sure to check out the first article in this series, Permissions in Django REST Framework.

AllowAny

The most open permission of all is AllowAny. The has_permission and has_object_permission methods on AllowAny always return True without checking anything. Using it isn't necessary (by not setting the permission class, you implicitly set this one), but you still should since it makes the intent explicit and helps to maintain consistency throughout the app.

You specify it by including permission_classes in your view:

from rest_framework import viewsets
from rest_framework.permissions import AllowAny

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [AllowAny] # built-in permission class used

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

Anyone, even unauthenticated users, can access the API endpoint using any HTTP request method:

DRF Permissions Execution

IsAuthenticated

IsAuthenticated checks if the request has a user and if that user is authenticated. Setting permission_classes to IsAuthenticated means that only authenticated users will be able to access the API endpoint with any of the request methods.

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [IsAuthenticated] # permission class changed

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

The unauthenticated user now gets access denied:

DRF Permissions Execution

IsAuthenticatedOrReadOnly

When permissions are set to IsAuthenticatedOrReadOnly, the request must either have an authenticated user or use one of the safe/read-only HTTP request methods (GET, HEAD, OPTIONS). This means that every user will be able to see all the messages, but only logged-in users will be able to add, change, or delete objects.

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [IsAuthenticatedOrReadOnly] # ReadOnly added

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

An unauthenticated user can view the message that was posted by an authenticated user, but they can't do anything with it or add their own:

DRF Permissions Execution

IsAdminUser

Permissions set to IsAdminUser means that the request needs to have a user and that user must have is_staff set to True. This means that only admin users can see, add, change, or delete objects.

from rest_framework import viewsets
from rest_framework.permissions import IsAdminUser

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [IsAdminUser] # only for admin users

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

The interesting part here is that unauthenticated users and authenticated users without admin access will get different errors.

For unauthenticated users, a NotAuthenticated exception is raised:

DRF Permissions Execution

Meanwhile, for an authenticated user without admin access, a PermissionDenied exception is raised:

DRF Permissions Execution

DjangoModelPermissions

DjangoModelPermissions allows us to set any combination of permissions to each of the users separately. The permission then checks if the user is authenticated and if they have add, change, or delete user permissions on the model.

from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissions

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [DjangoModelPermissions]

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

Unlike with other permissions, this is not the end of setting the permission. You need to set the permissions for the specific user:

Django Admin

If you check the single view for the post, you can see that this specific user can edit the post, but can't delete it:

DRF Permissions Execution

DjangoModelPermissions doesn't necessarily need to be assigned to a single user. You can use it for group permissions also. Here's a group that's allowed to delete the message:

Django Admin

And you can see here, that the member of that group can delete the message:

DRF Permissions Execution

DjangoModelPermissions must only be applied to views that have a queryset property or a get_queryset() method.

For example, the CreateAPIView generic view doesn't require a queryset, so if you set DjangoModelPermissions on it you'll get an assertion error. However, if you perform the querying, even though you're not using the queryset, DjangoModelPermissions will work:

class NewMessage(generics.CreateAPIView):
    queryset = Message.objects.all()
    permission_classes = [DjangoModelPermissions]
    serializer_class = MessageSerializer

DjangoModelPermissionsOrAnonReadOnly

DjangoModelPermissionsOrAnonReadOnly extends the DjangoModelPermissions and only changes one thing: It sets authenticated_users_only to False.

from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [DjangoModelPermissionsOrAnonReadOnly]

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

Anonymous users can see the objects but can't interact with them:

DRF Permissions Execution

DjangoObjectPermissions

While DjangoModelPermissions limits the user's permission for interacting with a model (all the instances), DjangoObjectPermissions limits the interaction to a single instance of the model (an object). To use DjangoObjectPermissions you'll need a permission backend that supports object-level permissions. We'll look at django-guardian.

While there are quite a few packages that cover Django permissions, DRF explicitly mentions django-guardian, which is why we're working with it in this article.

Other packages that deal with object-level permissions:

  1. drf-extensions
  2. django-oso
  3. django-rules
  4. django-role-permissions

Installing django-guardian

To use django-guardian, you first need to install it:

$ pip install django-guardian

Then, add it to the INSTALLED_APPS and the AUTHENTICATION_BACKENDS:

# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'guardian',
]

# ...

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'guardian.backends.ObjectPermissionBackend',
)

Finally, run the migrations:

(venv)$ python manage.py migrate

If you want to see the permissions for a single object, you need to use GuardedModelAdmin instead of ModelAdmin. You can do that in an admin.py file like so:

# admin.py

from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from tutorial.models import Message

class MessageAdmin(GuardedModelAdmin):
    pass

admin.site.register(Message, MessageAdmin)

Now, if you open a single object in the admin panel, on the top right there's a new button called "OBJECT PERMISSIONS". Upon clicking on it, the object permissions panel opens:

Django Admin

Using DjangoObjectPermissions

Now knowing the difference between has_permission and has_object_permission will come in handy. In short, DRF first checks if the request has permission to access the model. If it doesn't, DRF doesn't care about the object-level permissions.

DRF Permissions Execution

That means that the user must have model permission if you want the object-level permission to be checked. A good use case for object-level permissions is for only allowing the owner of an object to change or delete it. Here's a view that allows only the creator of the object to delete it:

# views.py

from guardian.shortcuts import assign_perm
from rest_framework import viewsets
from rest_framework.permissions import DjangoObjectPermissions

from .models import Message
from .serializers import MessageSerializer


class MessageViewSet(viewsets.ModelViewSet):

    permission_classes = [DjangoObjectPermissions] # class changed

    queryset = Message.objects.all()
    serializer_class = MessageSerializer

    def perform_create(self, serializer): # new function
        instance = serializer.save()
        assign_perm("delete_message", self.request.user, instance)

As you can see, there are two important changes:

  1. First, the permission class is DjangoObjectPermissions.
  2. Next, we tapped into the model instance creation. We can't assign permission to a non-existing object, so we first need to create the instance and after that, assign object permission to it via the assign_perm shortcut from django-guardian.

assign_perm is a django-guardian function used for assigning permissions to certain users or groups. It takes three arguments:

  1. permission: delete_message
  2. user_or_group: self.request.user
  3. object (defaults to None): instance

Again, for the object permission to work, the user must have model-level permission for the corresponding model. Let's say you have two users with the same permissions on the model:

Django Admin

And you use the code above to assign permission only to the creator. user_1 -- the creator of the object -- can delete it, but user_2 can't delete it even though they have model-level permissions:

DRF Permissions Execution

If we remove the permission to delete any object on the model for user_1:

Django Admin

They can't delete their message:

DRF Permissions Execution

Even though they have the permission to delete this particular object:

Django Admin

Global Permissions

You can easily set global permission in your settings.py file, using built-in permission classes. For example:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

DEFAULT_PERMISSION_CLASSES will only work for the views or objects that don't have permissions explicitly set.

You don't necessarily need to use built-in classes here. You can use your own custom classes as well.

Conclusion

That's it. You should now know how to use Django REST Framework's seven built-in permission classes. They vary from totally open (AllowAny) to mostly closed (IsAdminUser).

You can set permissions globally, on the model (DjangoModelPermissions), or on a single object (DjangoObjectPermissions). There are also classes that allow you to limit "unsafe" HTTP methods, but allow anyone the safe ones (IsAuthenticatedOrReadOnly, DjangoModelPermissionsOrAnonReadOnly).

If you don't have any specific requirements, the built-in classes should suffice for most cases. On the other hand, if you have some special requirements, you should build your own permission class.

--

Django REST Framework Permissions Series:

  1. Permissions in Django REST Framework
  2. Built-in Permission Classes in Django REST Framework (this article!)
  3. Custom Permission Classes in Django REST Framework

Špela Giacomelli (aka GirlLovesToCode)

Špela Giacomelli (aka GirlLovesToCode)

GirlThatLovesToCode is passionate about learning new things -- both for herself and for teaching others. She's a fan of Python and Django and wants to know everything there is about those two. When she’s not writing code or a blog, she’s probably trying something new, reading, or spending time outside with her family.

Share this tutorial

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.