HIGH broken authenticationfirestore

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.auth exists
  • Overly permissive read/write: Rules using allow read, write: if true
  • Missing deny rules: No explicit allow statements 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 != null prevents unauthenticated access
  • Email verification: request.auth.token.email_verified == true ensures verified accounts
  • Data validation: Checking request.resource.data.userId == userId prevents IDOR attacks
  • Explicit deny: The allow write: if false prevents 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 IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Can Firestore security rules prevent all broken authentication attacks?
Security rules are essential but not sufficient alone. They prevent unauthorized database access at the server level, but you should also validate data in your application code, use proper authentication flows, and implement rate limiting. Think of security rules as the last line of defense, not the only defense.
How does middleBrick detect broken authentication in Firestore specifically?
middleBrick tests both authenticated and unauthenticated access patterns, attempts IDOR attacks by manipulating document IDs, checks for missing authentication checks in security rules, and verifies that data access is properly scoped to the authenticated user. The scanner also looks for Firestore-specific anti-patterns like predictable document IDs and overly permissive collection access.