This guide provides a step-by-step approach to implementing custom roles and permissions using Django and Django Rest Framework with JWT authentication for secure access.
Ensure your Django project is configured with DRF and JWT authentication.
In settings.py
, add the following:
from datetime import timedelta
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": (
'rest_framework.permissions.IsAuthenticated',
),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
}
Permission
and Role
ModelsCreate custom models for Permission
and Role
, and link them to a Profile
model associated with the user.
from django.contrib.auth.models import User
from django.db import models
class Permission(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
class Meta:
db_table = "users_permission"
class Role(models.Model):
name = models.CharField(max_length=100)
permissions = models.ManyToManyField(Permission, related_name='roles')
class Meta:
db_table = "users_role"
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
roles = models.ManyToManyField(Role, related_name='profiles')
class Meta:
db_table = "users_profile"
# Signal to create a Profile when a User is created
from django.dispatch import receiver
from django.db.models.signals import post_save
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
This view handles user authentication and JWT token generation. The generated token includes user roles and permissions.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
import json, os
class LoginView(APIView):
authentication_classes = []
permission_classes = [AllowAny]
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
user = authenticate(username=username, password=password)
if user is None:
return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
token = RefreshToken.for_user(user)
token['user_id'] = user.id
token['username'] = username
roles = user.profile.roles.values_list('name', flat=True)
permissions = Permission.objects.filter(roles__in=user.profile.roles.all()).values_list('name', flat=True)
token['roles'] = list(roles)
token['permissions'] = self.generate_permissions(list(set(permissions)))
return Response({
"refresh": str(token),
"access": str(token.access_token),
})
def generate_permissions(self, permissions):
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
permissions_file = os.path.join(BASE_DIR, 'permissions.json')
with open(permissions_file) as f:
data = json.load(f)
modules = data.get("modules", {})
for module_name, module_data in modules.items():
apps = module_data.get("apps", {})
module_full_access = True
module_has_partial_access = False
for app_name, app_data in list(apps.items()):
attributes = app_data.get("attributes", {})
app_has_access = False
full_access = True
for perm in permissions:
if perm.startswith(f"{module_name}_{app_name}_"):
attribute = perm.split(f"{module_name}_{app_name}_")[1]
if attribute in attributes:
attributes[attribute] = True
app_has_access = True
for attribute, access in attributes.items():
if access is None:
full_access = False
app_data["access"] = "full_access" if full_access else (
"partial_access" if app_has_access else None)
if app_data["access"] is None:
apps.pop(app_name)
else:
module_has_partial_access = module_has_partial_access or (
app_data["access"] == "partial_access")
module_full_access = module_full_access and (
app_data["access"] == "full_access")
module_data["access"] = "full_access" if module_full_access else (
"partial_access" if module_has_partial_access else None)
if module_data["access"] is None:
modules.pop(module_name)
return data
These views handle user creation, role assignment, and permission creation.
class CreateUserView(APIView):
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
email = request.data.get("email", "")
first_name = request.data.get("first_name", "")
last_name = request.data.get("last_name", "")
if not username or not password or not email or not first_name or not last_name:
return Response({"error": "Some fields are missing"}, status=status.HTTP_400_BAD_REQUEST)
if User.objects.filter(username=username).exists():
return Response({"error": "Username is already taken"}, status=status.HTTP_400_BAD_REQUEST)
user = User.objects.create_user(
username=username,
password=password,
email=email,
first_name=first_name,
last_name=last_name,
is_staff=True,
is_active=True
)
return Response({"message": "User created successfully", "user_id": user.id}, status=status.HTTP_201_CREATED)
class AssignRoleView(APIView):
def post(self, request):
user_id = request.data.get("user_id")
role_id = request.data.get("role_id")
try:
user = User.objects.get(id=user_id)
role = Role.objects.get(id=role_id)
except User.DoesNotExist:
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
except Role.DoesNotExist:
return Response({"error": "Role not found"}, status=status.HTTP_404_NOT_FOUND)
user.profile.roles.add(role)
return Response({"message": "Role assigned successfully"}, status=status.HTTP_200_OK)
class CreatePermissionView(APIView):
def post(self, request):
name = request.data.get("name")
description = request.data.get("description", "")
if not name:
return Response({"error": "Permission name is required"}, status=status.HTTP_400_BAD_REQUEST)
permission = Permission.objects.create(
name=name, description=description)
return Response({"message": "Permission created successfully", "permission_id": permission.id}, status=status.HTTP_201_CREATED)
class CreateRoleView(APIView):
def post(self, request):
name = request.data.get("name")
permission_ids = request.data.get("permission_ids", [])
if not name:
return Response({"error": "Role name is required"}, status=status.HTTP_400_BAD_REQUEST)
role = Role.objects.create(name=name)
if permission_ids:
permissions = Permission.objects.filter(id__in=permission_ids)
role.permissions.set(permissions)
return Response({"message": "Role created successfully", "role_id": role.id}, status=status.HTTP_201_CREATED)
Create a custom permission class to check if the user has the required permissions.
from rest_framework.permissions import BasePermission
from rest_framework_simplejwt.tokens import AccessToken
from django.contrib.auth import get_user_model
class HasPermission(BasePermission):
def has_permission(self, request, view):
token = request.headers.get('Authorization')
if not token:
return False
try:
token = token.replace('Bearer ', '')
access_token = AccessToken(token)
user_id = access_token.get('user_id', 0)
User = get_user_model()
user = User.objects.get(id=user_id)
permissions = Permission.objects.filter(roles__in=user.profile.roles.all()).values_list('name', flat=True)
required_permission = view.permission_required
if required_permission in permissions:
return True
except Exception:
return False
return False
Use this custom permission in any view:
permission_classes = [HasPermission]
permission_required = 'can_add_item'
from django.urls import path
from .views import LoginView, CreateUserView, AssignRoleView, CreatePermissionView, CreateRoleView
urlpatterns = [
path("login/", LoginView.as_view(), name="login"),
path("create_user/", CreateUserView.as_view(), name="create-user"),
path('assign_role/', AssignRoleView.as_view(), name='assign_role'),
path('create_permission/', CreatePermissionView.as_view(), name='create_permission'),
path('create_role/', CreateRoleView.as_view(), name='create_role'),
]
This file defines the structure for different modules and their access levels.
{
"modules": {
"erp": {
"access": null,
"apps": {
"items": {
"access": null,
"attributes": {
"create": null,
"view": null,
"edit": null,
"delete": null,
"add_quantity": null,
"view_cost": null,
"edit_quantity": null,
"view_details": null,
"edit_details": null
}
}
}
}
}
}
Access levels:
To use the custom permission in your views, just add:
permission_classes = [HasPermission]
permission_required = 'your_permission_name'