Imagine you're throwing a party, and you're the host.
You have different types of guests coming in, each with their own level of access to your party.
Now you establish certain rules and conditions that grant permission to some specific people:
Guest List: Not everyone is allowed. You have a list of names that can join your party.
Bouncer: You hire a bouncer, who makes sure to enforce the rules and conditions you have mentioned.
Access Levels: Maybe you have certain rooms specially allocated for VIP people. So only people who are labelled as "VIP" in the guest list can enter the VIP rooms and not the people who are not.
And your bouncer will make sure of that.
Here the Bouncer is like the Permission Class in Django Rest Framework.
The bouncer checks if where the visitor is in the guest list or not (True
or False
) to join the party.
To access the VIP rooms, the bouncer also checks if the guy belongs to the VIP class.
This checking by the bouncer is similarly done through methods of Permission classes called has_permission()
and has_object_permission()
. This methods return boolean
value. If it's True, then the user is granted access, else denied.
Think of permission classes like a set of keys. Some keys unlock every door in the building, while others only open specific rooms (like the VIP room or the kitchen). If you don't have the right key, you're not getting in.
When working with Django Rest Framework and Authentication, we always need certain permission classes or decorators that grants or denies permission to a user accessing a view function or class.
Built-in permission classes
IsAuthenticated
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
Breakdown of the code:
The class
IsAuthenticated
inherits the classBasePermission
. Whenever you try to create a new custom permission class, you need to inherit this class as it contains the methodshas_permission()
andhas_object_permission()
for granting or denying permissions.has_permission()
: Return boolean value as said before. It has parameters: request and viewrequest contains information about the user which is an instance of the built-in User model of Django or maybe an instance of the Auth User model you have created
view has these properties:
kwargs: A dictionary of all keyword arguments passed to the view. This includes any arguments captured from the URL, such as the ID of the object being retrieved or updated.
request: The Django HttpRequest object for the current request. This includes information about the HTTP method, headers, user, and more.
This method checks if the
request.user
contains the User instance and if the user is authenticated. If both isTrue
, then the user is granted permission.
What happens when permission check fails?
How permission classes are written for:
— Class Based Views
class ProductView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [isAuthenticated]
def get(self, request, id):
# code . . .
— Function Based Views
@apiView(['GET'])
@authentication_classes([TokenAuthentication])
@permission_classes([isAuthenticated])
def get_product(self, request, id):
# code . . .
Permissions in REST framework are always defined as a list of permission classes.
Before running the main body of the view each permission in the list is checked. If any permission check fails, an
exceptions.PermissionDenied
orexceptions.NotAuthenticated
exception will be raised, and the main body of the view will not run.
Response when permission is denied.
When the permission checks fail, either a "403 Forbidden" or a "401 Unauthorized" response will be returned, according to the following rules:
When the request was successfully authenticated, but permission was denied.
Look into this code that demonstrate this rule:from rest_framework.permissions import BasePermission, IsAuthenticated from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.authentication import TokenAuthentication class CustomPermission(BasePermission): def has_permission(self, request, view): return False # deny access class TestView(APIView): authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated, CustomPermission] def get(self, request): return Response({"message": "This is a protected resource"}, status=status.HTTP_200_OK)
— An HTTP 403 Forbidden response will be returned.
IsAuthenticated
returnsTrue
but since thehas_permission()
ofCustomPermission
returnsFalse
, access is denied.The request was not successfully authenticated, and the highest priority authentication class does not use
WWW-Authenticate
headers.Look into this code that demonstrate this rule:
from rest_framework.permissions import BasePermission from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class CustomPermission(BasePermission): def has_permission(self, request, view): return False # deny access class TestView(APIView): authentication_classes = [] # no authentication classes permission_classes = [CustomPermission] def get(self, request): return Response({"message": "This is a protected resource"}, status=status.HTTP_200_OK)
— An HTTP 403 Forbidden response will be returned.
💡Authentication class usesWWW-Authenticate
headers that are set in response to challenge the client for not providing authentication credentials.Since we have not provided any authentication class here, the response status will be 403 instead of 401 as the Permission class check fails.
The request was not successfully authenticated, and the highest priority authentication class does use
WWW-Authenticate
headers.Here let's assume that the user has not provided authentication credentials.
The following code do have the authentication class included.
from rest_framework.permissions import BasePermission from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.authentication import TokenAuthentication class CustomPermission(BasePermission): def has_permission(self, request, view): return True # allow access class TestView(APIView): authentication_classes = [TokenAuthentication] permission_classes = [CustomPermission] def get(self, request): return Response({"message": "This is a protected resource"}, status=status.HTTP_200_OK)
— An HTTP 401 Unauthorized response, with an appropriate
WWW-Authenticate
header will be returned.
Writing your own Permission Class
Let's say there is a django app that has two types of users
Student
Teacher
Now obviously there will be different APIs and view logic for different student and teachers.
For example, teacher can grade students on subjects, but the opposite does not work.
Below is a Class Based View that allows teacher to grade students:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from rest_framework import status
from .permissions import isTeacher
from .models import Score
class GradeStudentView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, isTeacher]
def post(self, request):
# Assuming the request contains data for the student's grade
# Example JSON data: {"student_id": 123, "grade": "A"}
student_id = request.data.get('student_id')
grade = request.data.get('grade')
# Model that stores grades and student id
score = Score(grade=grade, student=student_id)
score.save()
return Response({"message": f"Grade {grade} successfully assigned to student with ID {student_id}"},
status=status.HTTP_201_CREATED)
In the above code the list of permission classes checks whether the Class Based View is being accessed by user who is authenticated and also a Teacher.
It contains a custom permission class called isTeacher
.
The following code contains the isTeacher
Class that inherits BasePermission
class as it includes method like has_permission()
as discussed before:
from rest_framework.permissions import BasePermission
from .models import Teacher
class isTeacher(BasePermission):
message = "Only Teacher can grade his/her students."
def has_permission(self, request, view):
try:
teacher = Teacher.objects.get(user=request.user)
return True # This user is registered as Teacher, so access is allowed
except Teacher.DoesNotExist:
return False # This user is not a teacher so access is denied
Now what's happening here?
If you try to access the view as a student,
has_permission()
method ofisTeacher
will check if there exists an instance ofTeacher
It checks by searching for an instance by matching the
user
field of Teacher model with therequest.user
(which contains theUser
instance).For better understanding, let's assume the Teacher model looks like this:
from django.db import models from django.contrib.auth.models import User import uuid class Teacher(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField( User, related_name='teacher', on_delete=models.CASCADE) # more fields...
In the above code,
user = models.OneToOneField(...)
defines a one-to-one relationship between two models in Django -> User and Teacher.And as you know the
request.user
is an instance of User model anduser
field of any teacher instance is also an instance of User model, so we are trying to find the teacher instance withuser=request.user
.If such instance exists, then the user is a Teacher, else a Student.
And if it's a student, an exception is raised:
exceptions.PermissionDenied
which results in a response with the HTTP status code "403 Forbidden" along with the message set in the custom permission class above : "Only Teacher can grade his/her students."
Understanding the use of permission classes can help you build secure and robust application. Hope you guys enjoyed reading it!
The End... :)
Comment about the next concept or topic you would like to know.
➕ Follow for more such content.
👍 Like if you found this article useful :)
💬 Feedback would be appreciated.
🔗 Share if possible :D
Peace ✌️
Follow me for more such content :)