Almost all of the apps out there have some concept of authenticating and authorizing users in their system. In this blog post, we will look at how you can implement per-object authorization in Django.

Django’s Authorization (Permission) System

Django already has an “out-of-box” authorization mechanism. It relies on the concept of creating permission groups in your application and assigning users to it. Example: Let’s say in an application concerning schools, you will have a privileged group called teachers and only users belonging to this group can update scores of a test result.

There are a couple of drawbacks to the “out-of-the-box” implementation:

  • Permission rules can only be associated with one model in your application.
  • Rules cannot be associated with individual objects. They either apply to all the objects on the model or not at all.
  • It implicitly assumes that the user has read permission over the objects in our model. The three types of model permissions are: add, change and delete.

Implementing per-object permissions

To implement per object permissions for our application, we would be building our own permission system. To begin with, we would need a place to persist data about permissions. Since, we would need to support multiple models through one permission system; we cannot use Foreign keys. One might be inclined to use Generic Foreign Keys, but they have a performance overhead. Authorization checks are usually in the critical path of serving a user request, and we should aim to keep them as fast as possible.

My recommendation is to keep the schema for this table as simple as possible.

Custom Permission Table:
    object_id
    permission
    user_id

This would roughly translate to django as follows (Assuming that we are using uuid’s as our primary keys):


PERMISSION_TYPES = (
    ("Add", "add"),
    ("Change", "change"),
    ("Delete", "delete")
)

class CustomPermissionTable(models.Model):
    object_id = models.UUIDField(
        db_index=True,
        null=False,
        blank=False,
        help_text="Object for which permission_level has been defined.",
    )
    permission = models.CharField(
        max_length=30,
        choices=PERMISSION_TYPES,
        null=False,
        blank=False,
        help_text="Permission user_id has on object_id",
    )
    user_id = models.UUIDField(
        db_index=True,
        null=False,
        blank=False,
        help_text="Individual having permission on the object",
    )
    namespace = models.CharField(
        max_length=100,
        blank=False,
        help_text="Context in which the permission is being used"
    )

We can read the above model as a “user having user_id has permission on an object with object_id” in the context of namespace. Your namespace can be your app name or any other concept name in your system for which the permission system is being used.

Django allows us to create our own custom authorization backend and hook it in with the current backends. Assuming we have the straightforward implementation as given below:

from django.contrib.auth.backends import BaseBackend

class ObjectPermissionBackend(BaseBackend):
    def has_perm(self, user_obj, perm, obj=None):
        return models.CustomPermissionTable.objects.filter(
            user_id=user_id,
            object_id=object_id,
            permission=action
        ).exists()

We can add it to our AUTHENTICATION_BACKENDS setting, and our per object permission backend would work seamlessly with Django. For more information, please refer to the documentation.

AUTHENTICATION_BACKENDS = (
    'myprojects.customauthbackend.ObjectPermissionBackend',
)

To make this work with django admin, you can refer to this blog post.

If you want to use this authorization backend with drf, then you can create your own custom permission classes as per their documentation.

This blog post is just meant to give you a taste of how per object permission can look like in Django. Rather than hand-rolling a solution on your own, I would strongly suggest you use the full-featured django-guardian package for your authorization needs.

Related articles: