Broken Authentication in Firestore
How Broken Authentication Manifests in Firestore
Broken authentication in Firestore often stems from improper security rules or client-side logic that exposes data beyond intended boundaries. The most common pattern involves developers relying on client-side checks rather than Firestore's server-side security rules.
Consider this vulnerable pattern:
const db = firebase.firestore();
const doc = await db.collection('users').doc(userId).get();
if (doc.exists) {
const data = doc.data();
if (data.ownerId === currentUser.uid) {
return data;
} else {
throw new Error('Unauthorized');
}
}
This client-side validation is fundamentally broken because any authenticated user can modify the client code or tamper with requests. The security check must happen in Firestore security rules, not in application code.
Another critical vulnerability occurs with collection-level access. Developers often write rules like:
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
While this seems secure, it fails to account for enumeration attacks. An attacker can iterate through user IDs to discover valid accounts. More importantly, this rule doesn't prevent access to subcollections like users/{userId}/orders or users/{userId}/settings.
Firestore's document ID manipulation creates additional risks. Since document IDs are predictable and can be guessed, attackers can attempt:
// Predictable document IDs
const predictableDoc = db.collection('users').doc('admin');
const sequentialDoc = db.collection('users').doc('user123');
const emailDoc = db.collection('users').doc('[email protected]');
Without proper security rules, these document IDs become attack vectors for unauthorized data access.
Firestore-Specific Detection
Detecting broken authentication in Firestore requires examining both security rules and runtime behavior. Start by analyzing your security rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
}
}
Look for these red flags in your rules:
- Missing authentication checks: Rules that allow access without verifying
request.authexists - Overly permissive read/write: Rules using
allow read, write: if true - Missing deny rules: No explicit
allowstatements means default deny, but explicit deny rules prevent accidental exposure - Incomplete path coverage: Rules that don't cover all collection paths
middleBrick's Firestore-specific scanning tests authentication weaknesses by attempting authenticated and unauthenticated access to various endpoints. The scanner simulates:
// Test authenticated access patterns
const authenticatedRequests = [
{ method: 'GET', path: '/users/{randomId}' },
{ method: 'POST', path: '/users/{userId}/orders', data: { userId: 'test' } },
{ method: 'PUT', path: '/users/{userId}/profile', data: { email: '[email protected]' } }
];
// Test unauthenticated access patterns
const unauthenticatedRequests = [
{ method: 'GET', path: '/users/public' },
{ method: 'GET', path: '/users/{anyId}' },
{ method: 'POST', path: '/users/{anyId}/comments', data: { text: 'test' } }
];
The scanner also checks for common Firestore anti-patterns like allowing access based on predictable document IDs or missing validation of request data.
Firestore-Specific Remediation
Fixing broken authentication in Firestore requires a defense-in-depth approach using security rules, proper data modeling, and input validation. Start with robust security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can only access their own profile
match /users/{userId} {
allow read, write: if
request.auth != null &&
request.auth.uid == userId &&
request.time < request.auth.token.email_verified == true;
}
// Orders can only be created by the owning user
match /users/{userId}/orders/{orderId} {
allow read: if request.auth.uid == userId;
allow create: if request.auth.uid == userId &&
request.resource.data.userId == userId;
allow update, delete: if request.auth.uid == userId;
}
// Public data with explicit allow
match /public/{document=**} {
allow read: if true;
allow write: if false;
}
}
}
Key improvements in this ruleset:
- Explicit authentication:
request.auth != nullprevents unauthenticated access - Email verification:
request.auth.token.email_verified == trueensures verified accounts - Data validation: Checking
request.resource.data.userId == userIdprevents IDOR attacks - Explicit deny: The
allow write: if falseprevents accidental data modification
Complement security rules with application-level validation:
async function createOrder(userId, orderData) {
// Validate ownership before writing
if (orderData.userId !== userId) {
throw new Error('Order ownership mismatch');
}
// Sanitize and validate input
const sanitizedData = {
userId: userId,
items: orderData.items.filter(item =>
Array.isArray(item) && item.length > 0
),
total: parseFloat(orderData.total),
timestamp: firebase.firestore.FieldValue.serverTimestamp()
};
try {
await db.collection('users')
.doc(userId)
.collection('orders')
.add(sanitizedData);
return { success: true };
} catch (error) {
console.error('Order creation failed:', error);
throw new Error('Failed to create order');
}
}
For high-security scenarios, implement field-level security:
match /users/{userId}/profile/{field} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId &&
(field == 'displayName' || field == 'preferences');
}
This granular approach prevents unauthorized modification of sensitive fields like email or role while allowing updates to user preferences.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |