Exploring the Power of Firestore Rules: Best Practices and Examples

Sanjeeva Chitlapalli
Insights from ThoughtClan
9 min readFeb 21, 2024

--

Firestore, Google’s NoSQL document database, offers a robust security mechanism called Firestore Rules. These rules allow developers to control access to their database, ensuring that sensitive data remains protected while providing appropriate access to authorized users. In this comprehensive guide, we will delve into the intricacies of Firestore Rules, exploring best practices and providing real-world examples to demonstrate their power.

1. Understanding Firestore Security Rules

Firestore Security Rules provide a powerful mechanism to control access to your database, ensuring that your data remains secure and accessible to authorized users. By defining these rules, you can enforce authentication requirements, validate data integrity, and restrict access to specific collections or documents.

What are Firestore Security Rules?

Firestore Security Rules are a set of conditions and permissions that determine who can read, write, and modify data in your Firestore database. These rules are defined using a simple yet expressive syntax, allowing you to create fine-grained access controls based on your application’s requirements.

Integration with Firebase Authentication

One of the key benefits of Firestore Security Rules is its seamless integration with Firebase Authentication. By combining Firestore with Firebase Authentication, you can enforce user-based access controls, ensuring that only authenticated users can access specific data in your database.

The Role of Access Tokens

Access tokens play a crucial role in Firestore Security Rules. When a user authenticates with Firebase Authentication, they receive an access token that contains information about their identity and authorization. Firestore Security Rules can then use this access token to determine if a user has the necessary permissions to read, write, or modify data.

2. Getting Started with Firestore Rules

To begin utilizing Firestore Security Rules, it’s important to understand the basic syntax and structure. Firestore rules consist of match statements, which specify the path to a document, and allow expressions, which define the conditions for granting access to that document.

Firestore Rules Syntax

Firestore rules are written in a declarative manner, following a specific syntax. The service declaration specifies the Firestore service, while match statements define the path to a document or collection. The allow expression is used to specify the conditions for granting read or write access.

rules_version = '2';

service cloud.firestore {
match /databases/{database}/documents {
match /collection/{document} {
allow read, write: if <condition>;
}
}
}

Defining Match Statements

Match statements are used to specify the path to a document or collection within your Firestore database. You can use wildcards, such as {document=**}, to allow rules to apply to multiple documents or collections. By using these match statements, you can define fine-grained access controls based on the specific paths within your database.

Allowing Read and Write Operations

The allow expression is used to define the conditions for granting read or write access to a document or collection. By specifying allow read or allow write, you can control the type of access that is allowed. The conditions within the if statement are evaluated to determine if the access should be granted.

3. Basic Rule Patterns

Let’s explore some basic rule patterns that are commonly used in Firestore Security Rules. These patterns will help you understand how to define access controls and restrictions for your database.

Denying All Reads

In certain scenarios, you may want to deny all read access to your database. This can be useful when you have sensitive data that should not be exposed to any user. By using the if false condition, you can deny all read operations.

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

Denying All Writes

Similar to denying reads, you may also want to deny all write operations to your database. This can be useful when you want to ensure that data remains static and cannot be modified. By using the if false condition, you can deny all write operations.

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

Granting Access to Authenticated Users

To allow access only to authenticated users, you can use the request.auth object in your rules. By checking if request.auth.uid is not null, you can ensure that only authenticated users can read or write data.

match /{document=**} { allow read, write: if request.auth.uid != null;}

Ensuring User-Specific Access

To enforce user-specific access controls, you can check if the requesting user’s ID matches the ID stored in the document being accessed. This pattern is useful when you want to restrict users to their own data.

match /users/{userId} { allow read, write: if request.auth.uid == userId;}

4. Advanced Rule Patterns

Let’s explore some advanced rule patterns that can be used to enforce more complex access controls and data validations in Firestore Security Rules.

Scoped Rules for Specific Operations

In some cases, you may want to define different conditions for specific read or write operations. For example, you may want to allow users to read documents but restrict their ability to list all documents in a collection. By using scoped rules, you can define different conditions for get, list, create, update, and delete operations.

match /collection/{document} {
// Applies to read operations on a single document
allow get: if <condition>;

// Applies to list operations on the entire collection
allow list: if <condition>;

// Applies to create operations
allow create: if <condition>;

// Applies to update operations
allow update: if <condition>;

// Applies to delete operations
allow delete: if <condition>;
}

Recursive Wildcards for Hierarchical Data

Firestore allows you to organize data in collections and subcollections, creating a hierarchical structure. To apply rules to all documents within a collection and its subcollections, you can use recursive wildcards. By using {name=**}, you can match any document within the specified path, even if it is located in a deeply nested subcollection.

match /collection/{document=**} {
allow read, write: if <condition>;
}

Checking Data Validity with Conditions

Firestore Security Rules enable you to validate data before allowing write operations. For example, you can check if a specific field has a certain value or if the length of a value meets certain criteria. By using conditions, you can ensure that data being written to your database is valid and meets your requirements.

match /collection/{document} {
allow write: if request.resource.data.field == 'value' && request.resource.data.value.length > 5;
}

5. Common Functions for Firestore Rules

Firestore Security Rules support the use of custom functions, which can be handy for simplifying complex conditions and enhancing code reusability. Let’s explore some common functions that can be used in Firestore Rules.

Verifying Value Types

You can create custom functions to verify the type of a value being written to your database. By using these functions, you can ensure that only values of a specific type are allowed.

function isString(value) {
return value is string;
}

function isNumber(value) {
return value is number;
}

match /collection/{document} {
allow write: if isString(request.resource.data.field) && isNumber(request.resource.data.value);
}

Validating Value Inclusion in a List

In certain cases, you may want to validate if a value belongs to a list of allowed values. By creating a custom function, you can check if a value is included in a predefined list.

function isIncluded(value, allowedValues) {
return value in allowedValues;
}

match /collection/{document} {
allow write: if isIncluded(request.resource.data.field, ['value1', 'value2', 'value3']);
}

Enforcing Value Length Restrictions

You can also enforce length restrictions on values being written to your database. By using a custom function, you can check if the length of a value meets your specified criteria.

function isLengthValid(value, minLength, maxLength) {
return value.length >= minLength && value.length <= maxLength;
}

match /collection/{document} {
allow write: if isLengthValid(request.resource.data.field, 5, 10);
}

6. Ensuring Security with Nested Rules

Firestore Security Rules allow you to nest match blocks to create more complex access controls. By nesting match blocks, you can define rules for specific paths within your database, ensuring that access is granted or denied according to your requirements.

Accessing Other Documents with get() and exists()

With Firestore Security Rules, you can access other documents in your database to perform additional checks or validations. The get() function allows you to retrieve the state of a document, while the exists() function checks if a document exists.

match /collection/{document} {
allow write: if exists(/databases/$(database)/documents/users/$(request.auth.uid));
}

Transactional Operations with getAfter()

In Firestore, you can perform transactional operations using the getAfter() function. This function allows you to access the state of a document after a transaction, enabling you to define complex rules that involve multiple write operations.

match /collection/{document} {
allow write: if getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}

Access Call Limits and Pricing Considerations

It’s important to be aware of the access call limits in Firestore Security Rules. Firestore enforces limits on the number of document access calls per rule set evaluation, which can impact the performance and cost of your application. Make sure to optimize your rules and consider caching to minimize the impact on your Firestore usage and billing.

7. Deploying and Testing Firestore Rules

Once you’ve defined your Firestore Security Rules, you need to deploy them to your Firestore database. Firestore provides multiple deployment options, including using the Firebase Console, Firebase CLI, or the Firestore REST API. After deployment, it’s crucial to test your rules thoroughly to ensure they function as expected.

Deploying Rules with Firebase Console

To deploy your Firestore Security Rules using the Firebase Console, navigate to the Firestore section and open the Rules tab. Write your rules in the online editor and publish them to apply the changes.

Deploying Rules with Firebase CLI

Using the Firebase CLI allows you to deploy your Firestore Security Rules as part of your existing deployment process. Set up Firestore in your project directory and create a .rules file to define your rules. Finally, use the Firebase CLI to deploy the rules to your Firestore database.

firebase init firestore
firebase deploy - only firestore

Using the Firestore Rules Simulator for Testing

Firestore provides a Rules Simulator that allows you to test your ruleset before deploying it to your production environment. The Rules Simulator allows you to simulate authenticated and unauthenticated reads, writes, and deletes, helping you ensure that your rules are working as intended.

8. Best Practices for Firestore Rules

To ensure the security and efficiency of your Firestore database, it’s important to follow best practices when defining your Firestore Security Rules. Here are some key recommendations to keep in mind:

  • Secure by Default: Deny all access by default and only allow specific operations as needed.
  • Granular Access Control: Define fine-grained access controls for different operations and user roles.
  • Leverage Firebase Authentication: Integrate Firestore Security Rules with Firebase Authentication to enforce user-based access controls.
  • Validate Data Integrity and Ownership: Use conditions to validate data integrity and check if users have ownership of specific documents.
  • Optimize Rules and Usage: Minimize the number of access calls, consider caching, and regularly review and optimize your ruleset.

9. Real-World Examples of Firestore Rules

Let’s explore some real-world examples of Firestore Security Rules to understand how they can be applied in practical scenarios.

Securing Public and Protected Data Collections

Imagine you have a Firestore database with three collections: publicdata, protecteddata1, and protecteddata2. You want to ensure that only authenticated users can access the protected data collections while allowing read-only access to the public data collection. You can achieve this by defining specific rules for each collection.

match /publicdata/{document} {
allow read;
}

match /protecteddata1/{document} {
allow read, write: if request.auth.uid != null;
}

match /protecteddata2/{document} {
allow read, write: if request.auth.uid != null;
}

Allowing Read-Only Access for Public Data

If you have a collection that contains public data that should be accessible to all users, you can define rules to allow read access for everyone while restricting write access to authenticated users only.

match /publiccollection/{document} {
allow read;
allow write: if request.auth.uid != null;
}

Securing User-Specific Data with Authenticated Access

To ensure that users can only access their own data, you can enforce user-specific access controls. By checking if the requesting user’s ID matches the ID stored in the document being accessed, you can restrict access to user-specific data.

match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}

10. Conclusion: Harnessing the Power of Firestore Rules

Firestore Security Rules offer a powerful and flexible mechanism for controlling access to your Firestore database. By using these rules, you can enforce authentication requirements, validate data integrity, and restrict access to specific collections or documents. By following the best practices and examples outlined in this guide, you can ensure the security and efficiency of your Firestore database. Harness the power of Firestore Rules and build secure and robust applications with ease.

--

--