Skip to content

Firestore Security Rules: Complete Access Control Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Firestore Security Rules: Complete Access Control Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Firestore Security Rules control read and write access to your database by evaluating each request against conditions like user authentication, document data, and request path patterns.

What You'll Learn

How to write Firestore Security Rules that restrict access to authorized users, validate data structure, implement role-based permissions, and test rules before deploying.

Why It Matters

Misconfigured security rules are the most common Firebase vulnerability. DodaTech's Durga Antivirus Pro uses rules to ensure users can only read their own devices and scan data — preventing data leaks across tenants.

Real-World Use

A multi-tenant security app: each user sees only their own devices. Admins can read all data. Write operations validate threat levels and prevent malicious data injection.

flowchart LR
    A["Request\nRead device/doc"] --> B["Security Rules\nEvaluate"]
    B --> C{"request.auth\n!= null?"}
    C -->|No| D["DENY"]
    C -->|Yes| E{"resource.data.userId\n== request.auth.uid?"}
    E -->|No| D
    E -->|Yes| F["ALLOW"]
    style D fill:#fecaca,stroke:#dc2626
    style F fill:#bbf7d0,stroke:#16a34a

Basic Auth Check

// Allow access only to authenticated users
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // All authenticated users can read
    match /{document=**} {
      allow read: if request.auth != null;
    }

    // Only authenticated users can write
    match /{document=**} {
      allow write: if request.auth != null;
    }
  }
}

User-Owned Data Pattern

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Each user can only read/write their own documents
    match /users/{userId} {
      allow read, write: if request.auth != null
                      && request.auth.uid == userId;
    }

    // Devices belong to a user
    match /users/{userId}/devices/{deviceId} {
      allow read: if request.auth != null
               && request.auth.uid == userId;
      allow write: if request.auth != null
                && request.auth.uid == userId
                && request.resource.data.userId == userId;
    }
  }
}

Role-Based Access Control

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Helper function to check user role
    function isAdmin() {
      return request.auth != null
          && get(/databases/$(database)/documents/admins/$(request.auth.uid))
              .data.role == 'admin';
    }

    // Admins can read all documents
    match /{document=**} {
      allow read: if isAdmin();
    }

    // Regular user access
    match /users/{userId}/devices/{deviceId} {
      allow read: if request.auth != null
               && (request.auth.uid == userId || isAdmin());
      allow write: if request.auth.uid == userId;
    }

    // Admin-only functions
    match /scans/{scanId} {
      allow read: if isAdmin();
      allow write: if false; // Written by Cloud Functions only
    }
  }
}

Data Validation

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /devices/{deviceId} {

      allow create: if request.auth != null
        // Validate required fields
        && request.resource.data.keys().hasAll(['name', 'os', 'userId'])
        // Validate field types
        && request.resource.data.name is string
        && request.resource.data.os is string
        // Validate field values
        && request.resource.data.os in ['Windows', 'macOS', 'Linux', 'Android', 'iOS']
        // Prevent setting protected fields
        && request.resource.data.role == null;

      allow update: if request.auth != null
        // Prevent changing owner
        && request.resource.data.userId == resource.data.userId;
    }
  }
}

Testing Rules in Console

// Use the Rules Playground in Firebase Console to test:
// - Simulate auth state (authenticated / unauthenticated)
// - Pick a document path
// - Choose read/write operation
// - View whether the rule allows or denies

// Example test scenario:
// Path: /users/user_abc123/devices/dev_001
// Auth: Authenticated with uid = "user_abc123"
// Operation: Read
// Expected: ALLOW

// Path: /users/user_abc123/devices/dev_001
// Auth: Authenticated with uid = "user_def456"
// Operation: Read
// Expected: DENY

Common Mistakes

1. Leaving Rules Open During Development

Default rules during development often allow read, write: if true. This is dangerous even in test mode. Use test credentials and restrict access from the start.

2. Not Using Granular Rules

Broad rules like match /{document=**} { allow read: if true; } expose your entire database. Scope rules to specific collections and paths.

3. Confirming Ownership with Only the Document ID

A user claiming userId = "admin" in the document data can fake ownership. Always use request.auth.uid — the server-verified auth token — not document data.

4. Ignoring the resource vs request.resource Distinction

resource is the existing document data. request.resource is the new data being written. Use request.resource to validate incoming writes, resource to check current state.

5. Not Handling Deletes

A delete operation triggers write rules. If you allow write but intend to prevent deletes, use allow delete: if false; explicitly.

Practice Questions

  1. What is the difference between resource and request.resource in rules?
  2. How do you check if a user is authenticated in Security Rules?
  3. What happens when a rule evaluates to false?
  4. How do you implement role-based access in Firestore rules?

Answers:

  1. resource is the current document data on the server. request.resource is the incoming data for writes. Use request.resource for create/update validation.
  2. Check request.auth != null. The request.auth.uid contains the user's unique ID from Firebase Auth.
  3. The request is denied. Firestore returns a permission-denied error to the client.
  4. Store roles in a users or admins collection. Use get() within rules to fetch the role and compare with required permissions.

Challenge: Write security rules for Durga Antivirus Pro that allow users full access to their own devices and scans, allow admins read access to everything, validate that scan reports don't exceed field limits, and prevent users from deleting scan records.

FAQ

Do Security Rules affect billing?

No. Rules run before any data access. Denied requests cost nothing. Allowed requests incur normal read/write charges.

Can I call external APIs from Security Rules?

No, rules are a declarative language without HTTP access. For external integrations, use Cloud Functions.

How do I test Security Rules before deploying?

Use the Rules Playground in Firebase Console. Simulate requests with specific auth states and document data to verify behavior.

Are Security Rules enough for data protection?

Rules enforce access control server-side. Combine with Firebase App Check to verify requests come from your app, and Cloud Functions for admin-only operations.

Can I use wildcards in match paths?

Yes, {document=**} matches any path. Use named wildcards like {userId} to capture path segments for use in conditions.

Mini Project

Write and test a complete Security Rules set: user-owned devices collection, admin read access, data validation on create, field protection on update, and delete prevention for scan records. Test each scenario in the Rules Playground.

What's Next

Firestore Indexes — optimize query performance with Composite and collection group indexes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro