Django REST Framework Permissions

Summary: in this tutorial, you’ll explore Django REST Framework permissions and learn how to define a custom permission class for the todo list project.

Introduction to the Django REST Framework permissions

Django REST Framework has some built-in permission settings that you can use to secure API. Django REST Framework allows you to set the permissions at three levels:

  • Project-level
  • View-level
  • Model-level

Project-level permissions

The project-level permissions are set in the single Django setting called REST_FRAMEWORK in the settings.py file of the Django project.

By default, Django REST Framework allows unrestricted access to the API. It’s equivalent to the following:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}Code language: Python (python)

Besides AllowAny permission classes, Django REST Framework also offers other built-in project-level permissions:

  • IsAuthenticated: only authenticated users have access.
  • IsAdminUser: only the admin/superusers have access.
  • IsAuthenticatedOrReadOnly: unauthenticated users can call any API but only authenticated users have to create, update, and delete privileges.

Let’s use the IsAuthenticated permission class:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}Code language: Python (python)

If you log out as a superuser and access the API endpoint that shows all the todos /api/v1/todos/, you’ll get the following HTTP 403 forbidden error message:

HTTP 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "detail": "Authentication credentials were not provided."
}Code language: Python (python)

The reason is that the API requires authenticated user.

Let’s create a regular user called testapi using the admin site http://localhost:8000/admin/1. Note that a regular user is not a superuser.

To enable the regular user testapi to log in and log out, you need to update the project-level URLconf to create the login/logout views:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('api.urls')),
    path("auth/", include("rest_framework.urls")), # added
]Code language: Python (python)

Once the rest_framework.urls is set, you can access the API endpoint /api/v1/todos/. A minor change is the login link on the right side:

If you click the Log In link, you’ll see the login form as follows:

Sign in to the browseable API using a regular user testapi and password. Once logged in successfully with the testapi user, you’ll see the following response:

View-level permissions

Django REST Framework allows you to add permissions at the view level for more granular control.

For example, you can update the TodoDetail view so that only admin users can view it but regular users cannot access it.

class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAdminUser,) # added
    queryset = Todo.objects.all()
    serializer_class = TodoSerializerCode language: Python (python)

The TodoDetail view has the permission_classes which is initialized to the permissions.IsAdminUser.

By doing this, Django REST Framework only allows the admin user to access the TodoDetail view, which maps to the API endpoint /api/v1/todos/id.

If you use the testapi user (regular user) to access the Todo detail API, you’ll get the HTTP 403 forbidden:

To log the testapi user out, you click the username link and select click logout link:

And login to the browsable API using the super admin (john).

Since the TodoDetail view allows the super admin to access, you can see the API response:

Custom Permissions

In the Todo project, you can restrict access so that only owners of todos can view, edit, update, and delete. But they cannot access the todos of other users. Also, the superuser will have access to the todo list of their own as well as manage the todos of other users.

To accommodate these permission requirements, you need to use Django REST Framework’s custom permissions.

To define custom permission, you use the BasePermission class of the Django REST Framework. Here’s the source code of the BasePermission class:

class BasePermission(metaclass=BasePermissionMetaclass):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return TrueCode language: Python (python)

The BasePermission has two methods:

  • has_permission(self, request, view) – Return True if permission is granted or False otherwise. The list views will call the has_permission method to check for permissions.
  • has_object_permission(self, requests, view, obj) – Return True if permission is granted or False otherwise. The detail view method will call has_permission() first. If passes, it’ll call the has_object_permission() method next to check for the permissions.

It’s a good practice to override both methods explicitly to avoid unwanted default settings of the base class.

The following illustrates the steps for defining and using a custom permission class in Django REST Framework:

First, define the IsOwnerOnly class that extends the BasePermission class in the permissions.py file:

from rest_framework import permissions


class IsOwnerOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        return True

    def has_object_permission(self, request, view, obj):
        if request.user.is_superuser:
            return True

        return obj.user == request.userCode language: Python (python)

The has_permission always returns True. It means that any user can access the TodoList view to get all the todos and create a new todo. Note that to restrict access to the todos of the current user, we’ll do it in the TodoList view later.

The has_object_permission method returns True if the current user is the super user or the owner of the todo.

Second, update the view classes in the views.py of the api app to use the custom permission class IsOwnerOnly:

from rest_framework import generics
from .serializers import TodoSerializer
from .permissions import IsOwnerOnly
from todo.models import Todo


class TodoList(generics.ListCreateAPIView):
    permission_classes = (IsOwnerOnly,)  # added
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    # added
    def filter_queryset(self, queryset):
        queryset = queryset.filter(user=self.request.user)
        return super().filter_queryset(queryset)


class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsOwnerOnly,)  # added
    queryset = Todo.objects.all()
    serializer_class = TodoSerializerCode language: Python (python)

Both TodoList and TodoDetail classes use the IsOwnerOnly class:

permission_classes = (IsOwnerOnly,)  # addedCode language: Python (python)

Also, add the filter_queryset method to the TodoList class to return only todos of the currently authenticated user:

def filter_queryset(self, queryset):
    queryset = queryset.filter(user=self.request.user)
    return super().filter_queryset(queryset)Code language: Python (python)

If you log in to the browsable API using the superuser, you can view, create, edit, update, and delete todos.

But if you log in using the regular user (testapi), you can see that the /api/v1/todos/ return an empty list.

Also, accessing the API endpoint /api/v1/todos/1/ will result in an HTTP 403 forbidden since the testapi user does not have access to the todo that does not belong to the user:

Now, you can use the testapi user to create a new todo with the following information:

{
    "title": "Test API",
    "completed": false
}Code language: Python (python)

Here’s the response:

HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 5,
    "title": "Test API",
    "completed": false,
    "user": "testapi"
}Code language: Python (python)

Make an HTTP GET request to the /api/v1/todos/ endpoint, you will get the todo list created by the testapi user:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "id": 5,
        "title": "Test API",
        "completed": false,
        "user": "testapi"
    }
]Code language: Python (python)

Also, you can test the view, update, and delete todo with id 5 using the testapi user. It should work as expected.

Download the project source code

Click the following link to download the project source code:

Download the project source code

Summary

  • Django REST Framework provides three permission levels including project level, view level, and model level.
  • Extend the BasePermisssion class to define custom permission to suit your requirements.
Did you find this tutorial helpful ?