Firebase Security Rules: The Complete Guide to Writing Secure Rules
OverMCP Team
TL;DR: How to Write Secure Firebase Security Rules
Firebase security rules are your app's first line of defense. To write secure rules, always validate authentication (request.auth != null), restrict access using granular path-based conditions, and never use wildcards without checks. Use the Firebase Rules Playground to test before deploying. This guide walks you through every pattern you need.
---
Why Firebase Security Rules Matter
If you're building an app with Firebase — especially with Firestore or Realtime Database — your security rules are the gatekeeper. Misconfigured rules are the #1 cause of data breaches in Firebase apps. AI coding tools often generate overly permissive rules (like allow read, write: if true;) to get things working fast. That's a disaster waiting to happen.
Let's fix that.
Core Principles of Secure Firebase Rules
1. Default Deny
Always start with a deny-all rule and explicitly allow only what's needed.
// Firestore rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false; // default deny
}
}
}2. Use Authentication Checks
Require the user to be logged in before accessing any data.
match /users/{userId} {
allow read, write: if request.auth != null;
}3. User-Based Access Control
Restrict access so users can only read/write their own data.
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}4. Validate Data on Write
Use request.resource.data to enforce data structure and values.
match /posts/{postId} {
allow create: if request.auth != null
&& request.resource.data.title is string
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100;
}Common Vulnerabilities and Fixes
🚩 Open Read/Write
Bad:
allow read, write: if true;Good:
allow read: if request.auth != null;
allow write: if request.auth.uid == resource.data.authorId;🚩 Wildcards Without Checks
Bad:
match /{document=**} {
allow read, write: if request.auth != null;
}Good:
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}🚩 Insecure Role Checks
Bad: Storing role in user document that user can edit.
Good: Use custom claims or a separate admin collection with write protection.
// Admin collection rules
match /admins/{userId} {
allow read: if request.auth != null;
allow write: if false; // Only set via Admin SDK
}
// Check admin status in other rules
match /sensitiveData/{docId} {
allow read: if request.auth != null
&& exists(/databases/$(database)/documents/admins/$(request.auth.uid));
}Step-by-Step: Writing Secure Rules for a Typical App
Imagine a simple social app with users, posts, and comments. Here's how to secure each collection:
Users Collection
match /users/{userId} {
allow read: if request.auth != null;
allow create: if request.auth != null && request.auth.uid == userId;
allow update: if request.auth.uid == userId
&& request.resource.data.email == resource.data.email; // prevent email change
allow delete: if false; // users can't delete themselves
}Posts Collection
match /posts/{postId} {
allow read: if request.auth != null;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update: if request.auth.uid == resource.data.authorId;
allow delete: if request.auth.uid == resource.data.authorId;
}Comments Collection (subcollection)
match /posts/{postId}/comments/{commentId} {
allow read: if request.auth != null;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth.uid == resource.data.authorId;
}Advanced Patterns
Granular Field-Level Security
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId
&& request.resource.data.keys().hasOnly(['displayName', 'photoUrl']);
}Quota and Rate Limiting
Firebase rules can't do time-based rate limiting directly, but you can check document counts:
match /posts/{postId} {
allow create: if request.auth != null
&& get(/databases/$(database)/documents/users/$(request.auth.uid)).data.postCount < 10;
}Use a counter in the user document updated via a Cloud Function.
Using Custom Claims
Set admin or premium roles via Firebase Admin SDK:
match /premiumContent/{docId} {
allow read: if request.auth.token.premium == true;
}Testing Your Rules
Before deploying, use the Firebase Console's Rules Playground to simulate requests. Test:
Tools to Help
Manual rule writing is error-prone. For vibe-coded apps, consider using a security scanner like OverMCP that automatically checks your Firebase rules for common misconfigurations. OverMCP scans your Firestore and Realtime Database rules against 50+ best practices and flags issues like open access, missing auth checks, and overly permissive wildcards.
FAQ
How do I prevent users from reading other users' data?
Use the pattern match /users/{userId} { allow read: if request.auth.uid == userId; }. This ensures a user can only read documents where the document ID matches their UID.
What's the difference between `resource.data` and `request.resource.data`?
resource.data refers to the existing document data (used in allow update and allow delete). request.resource.data refers to the new data being written (used in allow create and allow update). Use request.resource.data to validate incoming data.
Can I use Firebase rules to prevent SQL injection?
Firestore doesn't use SQL, so injection isn't a concern. However, always validate input data types and lengths to prevent malicious data storage. For Realtime Database, use newData to validate.