Firebase14 items

Firestore Security Rules Checklist

Checklist for writing secure Firestore rules

Last updated 2026-01-15

Quick Checklist

  • 1Remove all wildcard allow-all rules before launch
  • 2Write per-collection rules with specific conditions
  • 3Validate document schema in write rules
  • 4Enforce field-level permissions with request.resource.data
  • 5Use custom claims for role-based access control
  • 6Restrict list queries with query-based rules
  • 7Limit document size and field count in rules
  • 8Test all rules in the Firebase Emulator Suite
  • 9Prevent unauthorized field injection on updates
  • 10Use helper functions to DRY up rule logic
  • 11Set up rules deployment in CI/CD pipeline
  • 12Audit rules after every feature change
  • 13Restrict cross-collection access patterns
  • 14Version control your rules file

Firestore Security Rules Checklist

Firestore Security Rules are the gatekeepers of your Firebase data. They run server-side on every read and write operation and are the only thing standing between your data and the public internet. This checklist covers how to write rules that are both secure and maintainable.

1. Eliminate Open Rules

The Firebase Console often starts projects with test-mode rules that allow unrestricted access. These must be replaced before launch:

// REMOVE: Test-mode rules
match /{document=**} {
  allow read, write: if request.time < timestamp.date(2026, 3, 1);
}

Even time-limited open rules are dangerous. Attackers actively scan for Firebase projects with open databases, and your data can be exfiltrated in seconds.

2. Per-Collection Rules with Specific Conditions

Write rules for each collection individually. Avoid recursive wildcards ({document=**}) except for explicit deny-all defaults:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Default deny
    match /{document=**} {
      allow read, write: if false;
    }

    match /profiles/{userId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
        && request.auth.uid == userId;
    }

    match /orders/{orderId} {
      allow read: if request.auth != null
        && resource.data.userId == request.auth.uid;
      allow create: if request.auth != null
        && request.resource.data.userId == request.auth.uid;
    }
  }
}

3. Schema Validation

Rules should validate the structure and types of incoming data. Without validation, clients can write arbitrary fields or incorrect types that break your application:

match /posts/{postId} {
  allow create: if request.auth != null
    && request.resource.data.keys().hasAll(['title', 'content', 'authorId', 'createdAt'])
    && request.resource.data.keys().hasOnly(['title', 'content', 'authorId', 'createdAt', 'tags'])
    && request.resource.data.title is string
    && request.resource.data.title.size() > 0
    && request.resource.data.title.size() <= 200
    && request.resource.data.content is string
    && request.resource.data.authorId == request.auth.uid
    && request.resource.data.createdAt == request.time;
}

Use hasAll() to ensure required fields are present and hasOnly() to prevent field injection.

4. Role-Based Access Control with Custom Claims

For admin or moderator roles, use Firebase Auth custom claims instead of checking a Firestore document (which could be tampered with if rules are not perfectly written):

function isAdmin() {
  return request.auth != null
    && request.auth.token.admin == true;
}

match /admin/{document=**} {
  allow read, write: if isAdmin();
}

Set custom claims server-side using the Firebase Admin SDK:

await admin.auth().setCustomUserClaims(uid, { admin: true });

5. Query-Based Rules

Firestore rules can restrict list queries to prevent clients from scanning entire collections:

match /messages/{messageId} {
  allow list: if request.auth != null
    && request.query.limit <= 50
    && resource.data.channelId in get(/databases/$(database)/documents/members/$(request.auth.uid)).data.channels;
}

6. Helper Functions

Use functions to keep rules DRY and readable:

function isAuthenticated() {
  return request.auth != null;
}

function isOwner(userId) {
  return isAuthenticated() && request.auth.uid == userId;
}

function isValidString(field, minLen, maxLen) {
  return field is string && field.size() >= minLen && field.size() <= maxLen;
}

7. Preventing Unauthorized Field Modification on Updates

On update operations, verify that immutable fields have not changed:

allow update: if request.auth.uid == resource.data.authorId
  && request.resource.data.authorId == resource.data.authorId
  && request.resource.data.createdAt == resource.data.createdAt;

8. Testing with the Firebase Emulator

The Firebase Emulator Suite lets you run unit tests against your rules without hitting production:

import { assertSucceeds, assertFails } from '@firebase/rules-unit-testing';

it('allows users to read their own profile', async () => {
  const db = testEnv.authenticatedContext('user-123').firestore();
  await assertSucceeds(db.doc('profiles/user-123').get());
});

it('denies users from reading other profiles data', async () => {
  const db = testEnv.authenticatedContext('user-123').firestore();
  await assertFails(db.doc('profiles/user-456').get());
});

Run these tests in CI to catch rule regressions before deployment. Version control your firestore.rules file and treat changes with the same rigor as application code.

Use AuditYour.app to automatically detect open Firestore rules and common security anti-patterns.

Scan your app for this vulnerability

AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.

Run Free Scan