Broken Access Control in Django with Firestore
Broken Access Control in Django with Firestore — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when application logic fails to enforce proper authorization checks, allowing one user to access or modify data and functionality that should be restricted to others. In a Django application using Google Cloud Firestore as the backend, this risk is amplified by the potential mismatch between Django’s permission/authorization mechanisms and Firestore’s security model. If access controls are implemented only at the Django layer while Firestore rules remain permissive, or if Firestore rules rely on client-supplied data without strict validation, an authenticated user may be able to read, modify, or escalate privileges across other users’ resources.
Django typically manages authorization through model-level permissions, view decorators (e.g., @login_required, @permission_required), and object-level checks in business logic. Firestore, in its native mode, uses security rules to govern document and collection access based on request authentication and resource attributes. When Firestore rules are not tightly scoped — for example, allowing read access based only on collection group assumptions or on unvalidated request claims — a Django view that correctly identifies a user may still hand off to Firestore with rules that return more data than intended.
A concrete scenario: a Django view retrieves a user-specific document by concatenating a user ID from request.user with a Firestore document path. If the view does not re-validate ownership server-side and the Firestore rule is written as allow read: if request.auth != null;, any authenticated user can potentially iterate over document IDs and access other users’ data. This is a classic Broken Access Control pattern (BOLA/IDOR) where the access control decision is made in one layer but not enforced consistently in the other. The vulnerability is not in Django or Firestore alone, but in the integration where authorization assumptions are incomplete or duplicated across layers.
Additional exposure can happen when Firestore rules use administrative privileges from the client side (e.g., using a service account key in frontend code) or when Django passes raw user input into Firestore queries without strict allowlisting. These patterns can lead to privilege escalation or mass data exposure, which are covered under BOLA/IDOR and BFLA/Privilege Escalation checks in middleBrick’s scans. Because Firestore rules are evaluated at the database level, misconfigurations can bypass Django protections entirely, making cross-layer validation essential.
Firestore-Specific Remediation in Django — concrete code fixes
To secure Django applications using Firestore, authorization must be enforced both in Django and in Firestore security rules, with clear ownership checks and minimal privilege usage. Avoid relying solely on Firestore rules that grant broad read access to authenticated users. Instead, scope rules to specific document paths and validate user identity using UID-based references. On the Django side, always re-validate resource ownership before performing operations, and use Firestore transactions or batched reads to ensure consistency.
Example: a Django model representing a user profile stored in Firestore under a collection user_profiles, keyed by UID. The Firestore rule below restricts read and write access to the document whose ID matches the authenticated user’s UID:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /user_profiles/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
In Django, you should not trust path parameters alone. Use the authenticated user’s UID from request.user to build the document reference and confirm ownership before any Firestore operation:
from google.cloud import firestore
from django.http import Http404
from django.contrib.auth.decorators import login_required
@login_required
def get_user_profile(request):
db = firestore.Client()
user_ref = db.collection('user_profiles').document(request.user.uid)
doc = user_ref.get()
if not doc.exists:
raise Http404('Profile not found')
return JsonResponse(doc.to_dict())
For operations that involve multiple documents or collections, use Firestore queries with structured constraints and validate each result against the user context. For example, when a user can only access documents in user_data/{userId}/items/{itemId}, construct the reference explicitly and avoid collection group reads that do not enforce ownership:
def get_user_item(request, item_id):
db = firestore.Client()
item_ref = db.collection('user_data').document(request.user.uid).collection('items').document(item_id)
doc = item_ref.get()
if not doc.exists:
raise Http404('Item not authorized')
return JsonResponse(doc.to_dict())
When using service accounts in backend tasks, ensure the credentials are scoped to least privilege and never exposed to the client. Rotate keys regularly and monitor access patterns. middleBrick’s LLM/AI Security checks can help detect unintended exposure of service account logic or prompt injection risks in API handlers that interface with Firestore. For ongoing protection, the Pro plan’s continuous monitoring can alert on anomalous access patterns, while the CLI enables rapid, scriptable scans from the terminal using middlebrick scan <url>.