How to Implement Fine-Grained Authorization with Django

- Share:





2938 Members
Implementing Fine-Grained Authorization into your Django application allows you to control access at a highly detailed level, ensuring users only access the resources they are authorized to. In this guide, we’ll walk through how to set up Django authorization with fine-grained policies, from defining permissions to applying them across your application.
Authorization, the process of defining who or what can access specific resources or perform certain actions, is a critical aspect of security. It essentially resolves the question, "Who can do what?" in your application.
Django provides a robust built-in system known as "Django authorization," which includes "Django permissions" for managing access rights. However, this standard approach may not be sufficient for applications dealing with sensitive data or complex business logic, necessitating more customized authorization strategies.
Fine-grained authorization enables us to adjust access permissions to match the specific requirements of our application and its users.
For example, in a healthcare application, doctors may need access to patient records but only be allowed to view information relevant to their specialty, while administrators might require broader access for management tasks. By implementing fine-grained authorization, we can ensure that users have precisely the access they need, nothing more and nothing less, thereby enhancing security, compliance, and usability.
In this blog, we will learn how to implement fine-grained authorization in Django using Permit.io, a full-stack authorization-as-a-service solution.
Let us take an example of a hospital management system application and try to understand why standard authorization mechanisms in Django might not be enough.
This hospital management system helps to handle patient data, appointments, and staff roles. Within the system, doctors, nurses, and administrative staff have distinct roles and permissions. Doctors typically require access to a range of patient information to provide comprehensive care, including:
Nurses may only need to access specific details such as patient vitals. Additionally, access must be restricted based on the department to which a staff member belongs. For example, Cardiologists should not be able to access records belonging to Pulmonologists unless required for consulting or emergency purposes, ensuring data privacy and compliance.
Let's consider an example endpoint for retrieving patient information:
from django.http import JsonResponse
from django.views import View
class PatientDetailView(View):
def get(self, request, patient_id):
# Retrieve patient information from the database
patient = Patient.objects.get(pk=patient_id)
# Check if the user has permission to view this patient's information
if not request.user.has_perm('view_patient', patient):
return JsonResponse({'error': 'Permission denied'}, status=403)
# Serialize patient data
serialized_patient = serialize_patient(patient)
return JsonResponse(serialized_patient)
The code above specifies PatientDetailView class that handles GET requests.
patient_id.While we can enforce authorization in the above way, Django provides built-in middleware for handling authentication and authorization. One such middleware is django.contrib.auth.middleware.AuthenticationMiddleware, which associates users with requests based on session or token authentication.
To enforce permissions at the view level, Django provides the @permission_required decorator. Here's how we can apply it to our *PatientDetailView*:
from django.contrib.auth.decorators import permission_required
@permission_required('view_patient')
def patient_detail(request, patient_id):
# Retrieve patient information from the database
patient = Patient.objects.get(pk=patient_id)
# Serialize patient data
serialized_patient = serialize_patient(patient)
return JsonResponse(serialized_patient)
With this decorator, the view function will only be executed if the requesting user has the view_patient permission. Otherwise, Django will automatically return a 403 Forbidden response. The above examples provide us with a glimpse into how Django can help us implement a basic level of authorization by associating users with requests and checking for specific permissions.
Standard authorization mechanisms, such as RBAC (Role-Based Access Control), can become complex and difficult to manage as your application grows and requirements change. They often lack the flexibility to handle complex scenarios, such as conditional access based on multiple user attributes or contextual information.
Moreover, standard authorization mechanisms are often tightly coupled with the application's business logic, making the code harder to maintain and evolve. They also tend to be binary (i.e., grant or deny), lacking the ability to express nuanced policies.
To help you Implement fine-grained authorization into Django in an efficient and scalable way, we’ll use Permit.io, which is an end-to-end solution for managing user roles and permissions with a simple, intuitive UI. Permit allows us to define fine-grained access control policies that can take into account various factors, such as user attributes, resource attributes, and environmental factors. This makes it easier to handle complex authorization scenarios and adapt to changing requirements.
Permit also decouples authorization logic from the business logic, making your code cleaner and easier to maintain. It also provides a more expressive policy language, allowing you to define nuanced access control rules that can reflect the complex realities of your application's domain.
Fine-grained authorization goes beyond simple role-based access control (RBAC) by enabling us to define permissions with greater precision. It allows us to define authorization rules based on various factors, including:
We can create a more granular and secure authorization system that caters to the specific needs of the application by considering these aspects.
You can learn about the differences between RBAC, ABAC, and ReBAC in this comprehensive guide.
Permit.io is an authorization framework that can help us implement features like ABAC and ReBAC in our applications. It integrates seamlessly with our existing workflows and provides a user-friendly interface for managing authorization policies.
Let us take a look at a simple entity diagram representing our hospital management system:

The above PlantUML diagram represents the authorization requirement modeling for our hospital management system. Let's break down the elements and relationships:
Entities:
Relationships:
Permit.io Policy:
This represents the authorization policy implemented in the system. The policy applies to users, roles, patients, and departments, as indicated by the arrows pointing to these entities. Permit.io operates on three planes:
Now that we have a basic understanding of ABAC and ReBAC, let's see how we can implement ABAC in our hospital management system using Permit.io. We'll focus on defining an ABAC policy that grants access to patient records based on the user's department and the patient's department.
Before diving into the code:
Let's get started:
Step 1: Install Permit.io
To get started, we need to install the Permit.io package in our Django project. We can do this using pip:
pip install permit
Step 2: Define ABAC Policies
Next, we define our ABAC policies using Permit.io. For example, we can create a policy that allows doctors in the Cardiology department to view patient records belonging to the same department.
Here's an example policy definition:
from permit import Permit
from dotenv import load_dotenv
load_dotenv()
permit = Permit(
pdp=os.getenv("permit_pdp_url"),
token=os.getenv("permit_sdk_key")
)
permit.create_policy(
name='view_patient_record',
action='view',
resource='patient_record',
conditions=[
{
'attribute': 'user.department',
'operator': 'eq',
'value': 'Cardiology'
},
{
'attribute': 'patient.department',
'operator': 'eq',
'value': 'Cardiology'
}
]
)
To access your Permit.io PDP URL and API key, go to the connect-sdk page and select Python as your backend. Ensure you are signed in to Permit.io. If you prefer, you can replace os.getenv("permit_pdp_url") and os.getenv("permit_sdk_key") with the actual values.
In the above code snippet, we import the necessary modules and initialize Permit.io with the required configuration. For security, we load the Permit.io PDP URL and SDK key from environment variables.
We then create a policy named view_patient_record that allows the view action on patient_record resources. The policy specifies that the user's department must be Cardiology, and the patient's department must also be Cardiology for access to be granted.
Step 3: Check Authorization in the View
Now that we have defined our ABAC policy, we can check for authorization in our view.
Here's how we can modify our PatientDetailView to use Permit.io for authorization:
from django.http import JsonResponse
from django.views import View
from permit import Permit
from dotenv import load_dotenv
import os
load_dotenv()
permit = Permit(
pdp=os.getenv("permit_pdp_url"),
token=os.getenv("permit_sdk_key")
)
class PatientDetailView(View):
def get(self, request, patient_id):
# Retrieve patient information from the database
patient = Patient.objects.get(pk=patient_id)
# Check if the user has permission to view this patient's information
if not permit.check_permission(request.user, 'view_patient_record', patient):
return JsonResponse({'error': 'Permission denied'}, status=403)
# Serialize patient data
serialized_patient = serialize_patient(patient)
return JsonResponse(serialized_patient)
In the modified PatientDetailView above:
Permit class from the sync module in the permit package and initialize it with the required configuration.check_permission method, passing the requesting user, the policy name (view_patient_record), and the patient object.Pros of ABAC:
Cons of ABAC:
Situations to use ABAC:
Learn about the pros and cons of ABAC vs RBAC
Permit.io provides a user-friendly interface for defining and managing authorization policies.
Let's see how we can implement the ABAC policy we discussed earlier using the Permit.io web interface.
Step 1: Log in to Permit.io
Log in to the Permit web application using your credentials. If you don't have an account, you can sign up for a free account.

Workspace creation page
Step 2: Create a New Resource
Once signed in, you'll have a default project created for you. The project includes two environments:


Dashboard after initial signup
Click on the Development environment to start defining your resources. In our case, we want to create a resource for patient_record. Click on New Resource and enter the resource name as patient_record. You can add additional attributes to the resource if needed. In our case, we can add department as an attribute. Make sure to leave the actions as they are.

Create resources page
Step 3: Define New User Attributes
Next, let's define some user attributes. Click on the User Attributes tab and create a new attribute named department. This attribute will represent the department to which a user belongs.

User attributes page
Now we'll also add role attributes to our user. Inside the settings, select the Role Attributes tab and create a new attribute named doctor and patient. These attributes will represent the roles of a user.

User settings page
Step 4: Create a New ABAC Rules
Now, let's create an ABAC rule that allows doctors in the Cardiology department to view patient records in the same department. Click on the ABAC Rules tab and create a user set. Name the rule cardiology-staff and define the following conditions:
Doctor = True
Doctor ABAC rules
Follow the same steps to define the conditions and role of the cardiology-patients department. Define the following conditions:
cardiologyPatient = True
Patient ABAC rules
Once you've defined the conditions, save the rule.
Step 5: Create a New Policy
Now that we have our resources, user attributes, and ABAC rules defined, when we go to the Policy tab, we'll see a policy created for us. Select the dropdown for our patient_record resource. All of the actions we selected will be available for us to create a policy. We can now specify which users can perform which actions on the patient_record resource based on the ABAC rules we defined earlier.
In this case, I'll allow all actions with the role doctor and the department Cardiology to be able to view the patient_record resource. and only read action for the patient role and Cardiology department.

Final policies page
Once you've defined your policies in the Permit.io web interface, you can sync them with your Django application. Permit.io provides a Python SDK that allows you to fetch policies and make authorization decisions in your application.
Ensure you have a Permit.io account and have created the necessary policies in the Permit.io web interface. Also, store a Permit.io PDP URL and SDK key in environment variables.
Here's how you can sync your policies with Django:
from permit.sync import Permit
from dotenv import load_dotenv
from django.http import JsonResponse
from django.views import View
load_dotenv()
permit = Permit(
pdp= os.getenv("permit_pdp_url"),
token= os.getenv("permit_sdk_key")
)
class PatientDetailView(View):
def get(self, request, patient_id):
permitted = permit.check(request.user.email, "view", "patient_record")
if not permitted:
return JsonResponse({"error": "Access denied"}, status=403)
# Retrieve and serialize patient data
patient = Patient.objects.get(id=patient_id)
serialized_patient = PatientSerializer(patient).data
return JsonResponse(serialized_patient, status=200)
In the above code snippet, we import Permit.io and initialize it with the required configuration. We then check for permission using the check_permission method, passing the requesting user's email, the action (view), and the resource (patient_record). If the user has the necessary permission based on the defined policy, we proceed to retrieve and serialize the patient data. Otherwise, we return a 403 Forbidden response.
Relationship-Based Access Control (ReBAC) is an access control model in which permissions are granted based on relationships between the subject and object. In the context of our Django application, a ReBAC rule could be "a doctor can view a patient's record if the patient is one of their patients.”
Here's how you can implement ReBAC using Permit APIs:
Define ReBAC Rules:
First, define the ReBAC rules that govern the relationships between users and resources. In our case, we want to create a rule that allows a doctor to view a patient's record if the patient is one of their patients.
Here's an example rule definition:
from django.http import JsonResponse
from django.views import View
from .models import Patient
from .serializers import PatientSerializer
class PatientDetailView(View):
def get(self, request, patient_id):
# Retrieve the patient
patient = Patient.objects.get(id=patient_id)
# Check if the requesting user is the patient's doctor
permitted = permit.check(request.user.email, "view", f"patient_record:{patient_id}")
if not permitted:
return JsonResponse({"error": "Access denied"}, status=403)
# Serialize patient data
serialized_patient = PatientSerializer(patient).data
return JsonResponse(serialized_patient, status=200)
In the above code snippet, we retrieve the patient object based on the provided patient_id. We then check if the requesting user has the necessary permission to view the patient's record. The permission check is done using Permit.io's check method, which takes the user's email, the action (view), and the resource (patient_record:{patient_id}) as arguments. If the user has the required permission, we proceed to serialize and return the patient data. Otherwise, we return a 403 Forbidden response.
Pros of ReBAC:
Cons of ReBAC:
Learn more about the pros and cons of ReBAC vs RBAC here
Permit.io provides a flexible and powerful platform for implementing ReBAC in your Django application. By defining relationships between entities and creating rules based on those relationships, you can enforce fine-grained access control in your system.
Read more about ReBAC in Permit.io here.
Here's how you can implement ReBAC in Permit.io:
Let's edit our patient_record and add two new ReBAC options: doctor and patient. These will be linked later to related roles.

ReBAC options for the patient
We'll create an additional resource, p1, which derives from the main patient_record resource. This represents an individual patient's record. We'll then specify doctor and patient options for this resource.
In the relations section, add an option that specifies patient_record is a parent of p1. This will allow us to create a relationship between a doctor and a patient.

ReBAC options for *p1* resource
2. Create ReBAC Rules: Define ReBAC rules that govern access based on these relationships. For example, you might create a rule that allows a doctor to view a patient's record if they are the patient's treating physician.
Navigating over to the roles tab, we now see the roles doctor and patient that we created earlier for each resource. We can now link the patient_record resource to the doctor role and the p1 resource to the patient role. Select patient_record#doctor and p1#doctor and save.

ReBAC options doctor role
Repeat the same process for the patient role and the p1 resource.

ReBAC options for the patient
Your ReBAC rules are now defined and ready to be enforced in your Django application.
Once you've defined your ReBAC policies in the Permit.io web interface, you can sync them with your Django application. Permit.io provides a Python SDK that allows you to fetch policies and make authorization decisions in your application.
Here's how you can sync your ReBAC policies with Django:
from permit.sync import Permit
from dotenv import load_dotenv
load_dotenv()
permit = Permit(
pdp= os.getenv("permit_pdp_url"),
token= os.getenv("permit_sdk_key")
)
class PatientDetailView(View):
def get(self, request, patient_id):
permitted = permit.check(request.user.email, "view", f"patient_record:{patient_id}")
if not permitted:
return JsonResponse({"error": "Access denied"}, status=403)
# Retrieve and serialize patient data
patient = Patient.objects.get(id=patient_id)
serialized_patient = PatientSerializer(patient).data
return JsonResponse(serialized_patient, status=200)
While ABAC and ReBAC provide powerful and flexible authorization capabilities, there may be scenarios where other policy models are more suitable:
Role-Based Access Control (RBAC): For simpler authorization requirements, such as granting access based on predefined roles, RBAC can be an effective and easy-to-manage solution. Permit.io supports RBAC policies out-of-the-box. In our hospital management system, we could use RBAC to define basic roles like "Doctor", "Nurse", "Admin", etc. and associate permissions with each role. For example, the "Doctor" role could have permission to view and edit patient records, while the "Nurse" role may only have read access.
RBAC can also be useful for implementing feature flags or controlling access to specific application features based on a user's role. By defining roles like "TrialUser" or "PremiumUser", you can easily enable or disable certain functionality for different user groups. To implement feature flagging with RBAC and Permit.io:
To run the Django application, follow these steps:
git clone <https://github.com/tyaga001/django-authorization.git>
cd django-authorization
cd hsystem
pip install -r requirements.txt
python manage.py migrate
cp .env.example .env
# Add your credentials from Permit.io dashhboard to the env file above
python manage.py runserver 5000
The code above:
Add your Permit.io PDP URL and API_KEY to the .env file.

Permit.io environment credentials
Let’s set up the Permit.io PDP Microservice container:
If you do not have Docker installed as of yet, click here to install Docker.
Pull the container
Run the following command to pull the PDP Microservice container:
docker pull permitio/pdp-v2:latest
Run the container
Remember to replace <YOUR API KEY> with the Secret Key you obtained from your dashboard.
docker run -it -p 7766:7000 --env PDP_DEBUG=True --env PDP_API_KEY=<YOUR_API_KEY> permitio/pdp-v2:latest
If all goes well, you should be able to access the Django application at http://localhost:5000/, and the Permit.io PDP Microservice at http://localhost:7766/. Running the container locally allows for quicker application testing and development.

PDP microservice logs
Currently, when we run the application, we have a policy that allows doctors in the Cardiology department to view patient records in the same department, but our user is not allowed to view the patient record.

PDP container logs
To change the policy, you can navigate to the Permit.io web interface and modify the existing policy. In our case, we can change the policy to allow our users to view the patient record, and we are able to get the patient record.

Resource policy page

PDP logs on successful request
Need for Proactive Permissions Management
Proactive permissions management allows users to request or assign themselves certain roles or permissions, subject to approval by an authorized party. This can be particularly useful in dynamic environments where access needs can change rapidly.
For example, in a hospital, a doctor may need temporary access to patient records outside their department during an emergency. Instead of waiting for an administrator to grant this access, the doctor could request the necessary permissions through the application, which would then initiate an approval workflow. This not only streamlines access management but also reduces administrative overhead and response times.
Approval Workflow
An approval workflow for self-assigned roles and permissions might look like this:
A user requests additional roles or permissions through a feature in the application. The request is logged, and an alert is sent to the authorized personnel for review. The authorized personnel review the request. They may approve or deny the request based on the user's justification, their current roles, and the organization's policies. The user is notified of the decision. If the request is approved, the user is granted additional roles or permissions.
Using Permit API to Assign Roles
Permit.io's API can be used to dynamically assign roles or permissions to users upon approval. Here's a general idea of how you might do this:
from permit import Permit
from dotenv import load_dotenv
from django.http import JsonResponse
from django.views import View
load_dotenv()
permit = Permit(
pdp= os.getenv("permit_pdp_url"),
token= os.getenv("permit_sdk_key")
)
class AssignRoleView(View):
def post(self, request):
user = request.user
role = request.POST.get('role')
tenant = request.POST.get('tenant')
# Assuming you have a function to assign roles
ra = await permit.api.users.assign_role(user, role, tenant)
return JsonResponse({"message": "Role assigned successfully"}, status=200)
Fine-grained authorization (FGA) is crucial for secure and compliant applications. Attribute-Based Access Control (ABAC), Role-Based Access Control (ReBAC), and Policy-Based Access Control (PBAC) are models that enable FGA. They provide flexibility and control by defining access policies based on user attributes, roles, and predefined policies.
These models are particularly useful in complex and dynamic scenarios, allowing granular access rights management. This ensures users have exactly the access they need, enhancing security and compliance, especially in applications dealing with sensitive data or complex business logic.
Permit.io supports these models, providing a comprehensive authorization framework that integrates seamlessly with existing workflows. With its user-friendly interface and powerful policy authoring capabilities, it's a great solution for implementing and scaling fine-grained authorization in your applications.
Got questions? Need more guides? Want to learn more about Authorization? Join our Slack community, where there are hundreds of devs building and implementing authorization.

Full-Stack Software Technical Leader | Security, JavaScript, DevRel, OPA | Writer and Public Speaker