← All posts
firebase security rulesfirestore securityrealtime database securityvibe coding securityfirebase best practices

Firebase Security Rules: The Complete Guide to Writing Secure Rules

O

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:

  • Unauthenticated access (should be denied)
  • User accessing own data (should succeed)
  • User accessing another user's data (should be denied)
  • Malformed data (should be rejected)
  • 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.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free