HIGH broken access controldjangobearer tokens

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.

Frequently Asked Questions

How does middleBrick detect Broken Access Control with Bearer Tokens?
middleBrick runs 12 security checks in parallel, including Authentication, BOLA/IDOR, and Property Authorization. It tests unauthenticated attack surface behaviors and, when a valid Bearer Token is supplied, validates whether token-based authentication is coupled with object-level authorization and ownership filtering, surfacing missing checks with severity and remediation guidance.
Can middleware-only authentication be sufficient to prevent Broken Access Control?
No. Middleware or authentication-class-only approaches that set request.user are not sufficient; you must enforce ownership or role-based checks in each view or queryset (e.g., get_queryset filtering) and use object-level permissions to prevent horizontal and vertical access violations.