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