HIGH insecure direct object referencedjangobearer tokens

Insecure Direct Object Reference in Django with Bearer Tokens

Insecure Direct Object Reference in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Insecure Direct Object Reference (IDOR) occurs when an API exposes a reference to an internal object (such as a database primary key or sequential identifier) without verifying that the authenticated principal is authorized to access that specific object. In Django, using Bearer Tokens for authentication can inadvertently enable IDOR when token-based identity is used for authentication but not paired with object-level authorization checks. For example, consider a REST endpoint /api/users/<user_id>/ that relies on a Bearer Token in the Authorization header to identify the caller. The token may decode to a user ID (e.g., via a JWT payload), but if the view retrieves the target user record using a URL-supplied user_id without confirming that the authenticated user’s ID matches the requested user_id, an attacker can modify the URL to access or manipulate other users’ data.

Django REST Framework (DRF) commonly uses token authentication via TokenAuthentication or custom JWT handling. Bearer Tokens simplify client requests but do not enforce object-level permissions. An attacker can perform an IDOR by iterating over numeric or predictable IDs while authenticated with a valid Bearer Token. If the underlying queryset does not filter by the authenticated user (e.g., User.objects.get(pk=user_id) instead of User.objects.get(pk=user_id, id=request.user.id)), the API returns the requested resource regardless of the token owner’s entitlements. This vulnerability is not about token validity—it’s about missing authorization checks between the authenticated identity and the referenced object.

Real-world examples include endpoints that expose sensitive information such as /api/invoices/123/, /api/profiles/456/, or admin-only routes like /api/admin/users/789/. If the Bearer Token is valid but the view does not enforce that the authenticated subject owns or is permitted to operate on the referenced object, the scan will flag a BOLA/IDOR finding. This is a common root cause in applications that implement coarse-grained authentication (token-level) but neglect fine-grained authorization (object-level). The risk is compounded when IDs are predictable, and no additional checks such as ownership validation or permission decorators are applied.

In terms of detection, middleBrick’s BOLA/IDOR checks validate that each object reference is scoped to the authenticated subject. It confirms that views filter querysets by the authenticated user’s identity and that permission classes enforce object-level constraints. Without these safeguards, even well-formed Bearer Token implementations remain vulnerable to horizontal or vertical privilege escalation through predictable object references.

Bearer Tokens-Specific Remediation in Django — concrete code fixes

To remediate IDOR when using Bearer Tokens in Django, enforce object-level authorization by ensuring that every object reference is validated against the authenticated principal. Below are concrete code examples demonstrating secure patterns.

1. Use Django’s built-in permission mixins with object ownership

In class-based views, combine IsAuthenticated with a custom permission that checks object ownership. For example:

from rest_framework.permissions import IsAuthenticated, BasePermission
from rest_framework import viewsets
from .models import UserProfile
from .serializers import UserProfileSerializer

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        # Allow read-only permissions for any request,
        # but write permissions only to the object owner.
        if request.method in ('GET', 'OPTIONS', 'HEAD'):
            return True
        return obj.user == request.user

class UserProfileViewSet(viewsets.ModelViewSet):
    queryset = UserProfile.objects.all()
    serializer_class = UserProfileSerializer
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

    def get_queryset(self):
        # Ensure list endpoints also respect ownership if needed
        return UserProfile.objects.filter(user=self.request.user)

2. Explicitly filter querysets by authenticated user in function-based views

For function-based views, always filter by the authenticated user’s ID derived from the Bearer Token. Assuming the token payload contains or maps to a user ID:

from django.http import JsonResponse
from django.contrib.auth import get_user_model
from rest_framework.authentication import get_authorization_header
from rest_framework.exceptions import PermissionDenied

User = get_user_model()

def user_detail(request, user_id):
    auth_header = request.META.get('HTTP_AUTHORIZATION')
    if not auth_header or not auth_header.startswith('Bearer '):
        raise PermissionDenied('Authorization token required.')
    token = auth_header.split(' ')[1]
    # Assume decode_token returns a dict with 'user_id'
    payload = decode_token(token)  # implement securely
    authenticated_user_id = payload.get('user_id')
    if str(authenticated_user_id) != str(request.user.id):
        raise PermissionDenied('You can only access your own resource.')

    try:
        user = User.objects.get(pk=user_id, id=request.user.id)
    except User.DoesNotExist:
        raise PermissionDenied('Resource not found or access denied.')
    # serialize and return response

3. Apply scope-based filtering in DRF viewsets using the authenticated user

For endpoints where IDs are not owned but require tenant or scope isolation, filter by the authenticated subject:

from rest_framework import generics
from .models import Document
from .serializers import DocumentSerializer
from rest_framework.permissions import IsAuthenticated

class DocumentList(generics.ListAPIView):
    serializer_class = DocumentSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        # Ensure documents are scoped to the authenticated user
        return Document.objects.filter(owner=self.request.user)

4. Validate IDs against the authenticated subject in serializers

Add validation logic in serializer validate methods to reject mismatches:

from rest_framework import serializers

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['id', 'user', 'data']

    def validate(self, attrs):
        request = self.context.get('request')
        if request and hasattr(request, 'user'):
            if attrs.get('user') != request.user:
                raise serializers.ValidationError('You can only modify your own profile.')
        return attrs

These patterns ensure that Bearer Token authentication is complemented with object-level checks, preventing IDOR regardless of token validity. Always derive the subject from the token or session and filter querysets accordingly before exposing any object reference.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Can IDOR still occur if Bearer Tokens are rotated frequently?
Yes. Token rotation does not prevent IDOR; the vulnerability lies in missing object-level authorization. An attacker with a valid token can still manipulate object references if the backend does not scope queries to the authenticated subject.
How does middleBrick detect IDOR in Django APIs using Bearer Tokens?
middleBrick’s BOLA/IDOR checks compare the authenticated identity derived from the Bearer Token against the object referenced in the request. It verifies that querysets filter by the authenticated user’s ID and that permission classes enforce ownership or scope constraints.