Firestore Security Rules Cookbook

The purpose of this snippet to list common Firestore security rules patterns. Many of the rules below are extracted into functions to maximize code reuse.

Basic Recipes

Let’s start with some common use cases needed by almost every app.

At runtime, Firebase rules look for the first valid allow == true rule and NOT vice-versa. This is very important to keep in mind, as you might think you secured a path, only for it to be allowed somewhere else. Always start with secure rules, then carefully allow access where needed.

Locked Mode

Start here. Keep your database locked down by default, then add rules to grant access to certain read or writes. If you flip that value to true and your entire database will be open to the public.

file_type_firebase firestore rules
service cloud.firestore {
  match /databases/{database}/documents {

    match /{document=**} {
      allow read, write: if false;
    }

    // other rules go here...

  }
}

User-Based Rules

Most apps design their security rules around user authorization logic.

Secure to Signed-In Users

Allow access only when signed in. Example: a user must be logged in.

file_type_firebase firestore rules
    match /posts/{postId} {
        allow read: if request.auth != null;
    }

Secure by Owner, Has-One Relationship

Use this rule to allow access only if the authenticated user’s UID matches the ID on a document. Example: a user has-one account document.

file_type_firebase firestore rules
match /accounts/{userId} {
    allow write: if belongsTo(userId);
}

function belongsTo(userId) {
    return request.auth.uid == userId
}

Secure by Owner, Has-Many Relationship

Sometimes a user will own many documents in a collection, so the Document ID will be different than the User ID. In this case, we can look at the existing resource, assuming it has a uid property to track the relationship. Example: user has-many posts.

file_type_firebase firestore rules
service cloud.firestore {
  match /databases/{database}/documents {

    match /posts/{postId} {
        allow write: if isOwner(userId);
    }

    function isOwner(userId) {
        return request.auth.uid == resource.data.uid;
    }
  }
}

Advanced Scenarios

Make all Collections Readable or Writable - Except One

Let’s imagine you create collection names dynamically and want them to be unlocked by default. However, you have a special collection that requires strict rules. You start by locking down all paths, then dynamically pass the collection name in a rule. If the name does not equal the special collection then allow the operation.

file_type_firebase firestore rules
    match /{document=**} {
      allow read, write: if false;
    }

    match /{collectionName}/{docId} {
      allow read: if collectionName != 'special-collection';
    }

Common Functions

A few examples you might find useful

file_type_firebase firestore rules
    function isSignedIn() {
      return request.auth != null;
    }
    function emailVerified() {
      return request.auth.token.email_verified;
    }
    function userExists() {
      return exists(/databases/$(database)/documents/users/$(request.auth.uid));
    }

    // [READ] Data that exists on the Firestore document
    function existingData() {
      return resource.data;
    }
    // [WRITE] Data that is sent to a Firestore document
    function incomingData() {
      return request.resource.data;
    }

    function isUser(userId) {
      return request.auth.uid == userId;
    }

    function userEmail(userId) {
      return get(/databases/$(database)/documents/users/$(userId)).data.email;
    }

    // example application for functions
    service cloud.firestore {
      match /databases/{database}/documents {
        match /orders/{orderId} {
          allow create: if isSignedIn() && emailVerified() && isUser(incomingData().userId);
          allow read, list, update, delete: if isSignedIn() && isUser(existingData().userId);
        }

      }
    }

Q&A Chat