Broken Access Control in Django with Bearer Tokens
Broken Access Control in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Access Control (BAC) in Django when Bearer Tokens are used for API authentication commonly arises because token validation is implemented at the perimeter (e.g., in middleware or an authentication class) but authorization decisions are not consistently enforced at the view or object level. A BAC flaw occurs when a subject with a valid Bearer Token can access or manipulate resources they should not, such as other users’ data or admin-only endpoints, due to missing ownership checks or incorrect permission logic.
Django REST Framework (DRF) is frequently used to handle Bearer Tokens via packages like django-rest-framework-simplejwt or djangorestframework-simplejwt. If you configure token authentication but rely only on authentication (e.g., IsAuthenticated) and omit object-level permissions, an authenticated user can change URL parameters (IDs) to access or modify resources belonging to others. For example, an endpoint like /api/notes/{id}/ that only checks that the user is authenticated, without verifying that the note belongs to that user, enables BOLA/IDOR via Bearer Tokens. Attackers can enumerate IDs and escalate impact by leveraging privileges obtained through misconfigured token scopes or role assignments.
Another common pattern is using a custom authentication class that sets request.user from a token but does not integrate cleanly with Django’s permission system. If views use function-based decorators like @permission_classes([IsAuthenticated]) without additional checks, or class-based views omit get_queryset() filtering by owner, the unauthenticated attack surface includes horizontal privilege escalation across records. Insecure default configurations, such as permitting any authenticated token to call DELETE or PUT on generic views, further widen the vulnerability. Sensitive data exposure can occur when list endpoints return unfiltered querysets, allowing a token holder to iterate through other users’ private information. Because Bearer Tokens are often stored in browser storage or logs, compromised tokens amplify these risks, making robust access control essential.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
To remediate Broken Access Control with Bearer Tokens in Django, enforce object-level permissions and always filter querysets by the authenticated subject. Avoid relying solely on authentication; combine token validation with ownership or role checks. Below are concrete, working examples using DRF and simplejwt Bearer Tokens.
Example 1: Token authentication with ownership enforcement (class-based view)
from rest_framework import generics, permissions
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import Note
from .serializers import NoteSerializer
class NoteDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = NoteSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
# Ensure the queryset is filtered to the requesting user’s objects
return Note.objects.filter(owner=self.request.user)
def get_object(self):
# get_queryset() already filters by owner, so this is safe
return super().get_object()
Example 2: Token authentication with object-level permission classes
from rest_framework import permissions
class IsNoteOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of a note to access it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed for any safe method
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions only if the note's owner matches the token user
return obj.owner == request.user
from rest_framework import generics
from .models import Note
from .serializers import NoteSerializer
class NoteDetailWithObjectPermission(generics.RetrieveUpdateDestroyAPIView):
serializer_class = NoteSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated, IsNoteOwner]
def get_queryset(self):
return Note.objects.filter(owner=self.request.user)
Example 3: ViewSet with token authentication and per-viewset filtering
from rest_framework import viewsets
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import Note
from .serializers import NoteSerializer
class NoteViewSet(viewsets.ModelViewSet):
serializer_class = NoteSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
# Critical: scope to the token owner to prevent horizontal IDOR
return Note.objects.filter(owner=self.request.user)
Example 4: Using scopes or roles with token permissions
from rest_framework import permissions
from rest_framework_simplejwt.tokens import AccessToken
def get_token_user_role(token_value):
# Decode without verification only if you trust the token source; prefer verification
token = AccessToken(token_value)
return token.get('role', 'user')
class TokenScopePermission(permissions.BasePermission):
"""
Allow access based on token scopes/roles.
"""
def has_permission(self, request, view):
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return False
token_value = auth_header.split(' ')[1]
role = get_token_user_role(token_value)
if view.action == 'admin_action' and role != 'admin':
return False
return True
from rest_framework.views import APIView
from rest_framework.response import Response
class AdminOnlyView(APIView):
permission_classes = [TokenScopePermission]
def get(self, request):
return Response({'message': 'Admin access granted'})
Operational recommendations
- Always pair Bearer Token authentication with a filtered
get_queryset()in list and detail views to enforce ownership. - Use or implement object-level permission classes (e.g., IsNoteOwner) rather than only global permissions.
- Avoid exposing internal IDs directly when possible; consider random UUIDs for public-facing identifiers to reduce IDOR risk.
- Validate token scopes or claims at the permission level to enforce least privilege.