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 = TodoSerializer
Code language: Python (python)
The
view has the TodoDetail
permission_classes
which is initialized to the permissions.IsAdminUser
.
By doing this, Django REST Framework only allows the admin user to access the
view, which maps to the API endpoint TodoDetail
/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 True
Code language: Python (python)
The BasePermission
has two methods:
has_permission(self, request, view)
– ReturnTrue
if permission is granted orFalse
otherwise. The list views will call the
method to check for permissions.has_permission
has_object_permission(self, requests, view, obj)
– ReturnTrue
if permission is granted orFalse
otherwise. The detail view method will callhas_permission()
first. If passes, it’ll call the
method next to check for the permissions.has_object_permission
()
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.user
Code 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 = TodoSerializer
Code language: Python (python)
Both TodoList
and TodoDetail
classes use the IsOwnerOnly
class:
permission_classes = (IsOwnerOnly,) # added
Code 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.