Overview
Broken Function Level Authorization in Django occurs when a view or endpoint only enforces a broad, global permission and fails to enforce checks for each specific action within that function. As a result, an authenticated user may be able to perform operations on resources they should not own or access, such as updating, deleting, or approving items. Note: No CVE IDs are provided in this guide.
In real-world apps, endpoints often expose actions like publish, approve, or change status. If checks are placed only at the top of a function or rely on a blanket is_staff gate, attackers can abuse the endpoint to operate on other users' data, leading to data leaks, unauthorized edits, and compromised integrity.
This vulnerability manifests in Django patterns such as missing per-object checks in function-based or class-based views, insufficient filtering in get_queryset for DRF, or depending solely on model-level permissions rather than object-level constraints. Even with DRF, using broad permission_classes without object-level enforcement can create a window of privilege escalation.
Remediation involves adding per-object or owner-based checks, scoping queries, and using a proper permission backend. Recommended approaches include enabling per-object permissions with a backend like django-guardian, validating ownership or explicit permissions in every action, using DRF's DjangoObjectPermissions, and adding tests and audits to prevent regressions.
Code Fix Example
Django API Security Remediation
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
from django.core.exceptions import PermissionDenied
from .models import Item
# Vulnerable pattern: no per-object permission checks
def vulnerable_view(request, item_id):
item = Item.objects.get(id=item_id)
if request.method == 'POST':
item.name = request.POST.get('name', item.name)
item.save()
return JsonResponse({'status': 'updated', 'item_id': item.id})
# Fixed pattern: per-object permission checks (requires a back-end that supports object permissions,
# or explicit ownership checks)
def fixed_view(request, item_id):
if not request.user.is_authenticated:
raise PermissionDenied()
item = get_object_or_404(Item, id=item_id)
# Ownership check OR per-object permission check
if item.owner != request.user and not request.user.has_perm('yourapp.change_item', item):
raise PermissionDenied()
if request.method == 'POST':
item.name = request.POST.get('name', item.name)
item.save()
return JsonResponse({'status': 'updated', 'item_id': item.id})