Firebase Firestore — List of Essential Security Rules

Essential rules for securing your Firestore database.
Apr 19 2023 · 7 min read

Background

Firebase Firestore is a popular NoSQL document database widely used by developers to build scalable and flexible web and mobile applications.

While Firestore provides powerful features for data management, it is also essential to ensure the security of your data to prevent unauthorized access, data breaches, and other security threats.

This article will discuss essential rules for securing your Firestore database.

By implementing these security measures, you can ensure the safety and privacy of your data and protect your application from potential security vulnerabilities.

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

Now let’s see a list of common Firestore security rules you can use in a project.

Auth-based access control

Authenticated User access

To allow access only to authenticated users in Firestore security rules, you can use the following,

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

This rule uses the request.auth variable to check if the user making the request is authenticated. If the user is authenticated, they can read/write the document. If the user is not authenticated, they will be denied access.

Verified Email access

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /collection_name/{documentId} {
      allow read: if request.auth != null && request.auth.token.email_verified;
    }
  }
}

Owner-based access control

Single document owner-based access

If the user owns only one document and the authenticated user ID matches the document ID, then the user can write to the document.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /collectionName/{documentId} {
      // Allow the owner to write the document
      allow write: if request.auth.uid == documentId;
    }
  }
}

Here, we’re checking if the authenticated user ID matches the document ID directly. If the UIDs match, the user is allowed to write to the document. If they don’t match, write access is denied.

Multi-document owner-based access

If the user owns multiple documents and each document has the authenticated user ID, then the user should be allowed to write to all of their owned documents.

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /collectionName/{documentId} {
    // Allow the owner to write their owned documents
     allow write: if request.auth.uid == resource.data.userId;
   }
 }
}

In this case, each document in the collectionName collection has a field called userId, which contains the UID of the document owner.

The rule checks whether the request.auth.uid matches the userId field of the document being written to. If the UIDs match, the user is allowed to write to the document. If they don't match, write access is denied.

Document-level access control

Suppose you have a Firestore database containing a collection named Workspaces. Each document in this collection represents a workspace and contains information such as the workspace name, description, and members of the workspace.

To ensure that only members of a workspace can read its information, you can define a document-level access control rule as follows:

service cloud.firestore {
 match /databases/{database}/documents {
   // Allow read access to a workspace only if the user is a member of it
   match /workspaces/{workspaceId} {
     allow read: if exists(/databases/$(database)/documents/workspaces/$(workspaceId)/members/$(request.auth.uid));
   }
 }
}

In this rule, we are using the exists() function to check if the user's UID (which is available in request.auth.uid) exists as a document ID in the members subcollection of the workspace document.

By using this rule, only workspace members can read its information, while non-members will be denied access.

Role-based access control

This type of rule is useful in situations where you want to restrict access to certain features or content within your app to only certain types of users, such as editors or administrators.

Example 1 — To allow a user to write a document only if they have a role type of “editor”, you can use the following Firestore security rule:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   // Allow users with roleType of "editor" to write documents
   match /collectionName/{documentId} {
     allow read: if request.auth.token.roleType == 'editor';
     allow write: if request.auth.token.roleType == 'editor';
   }
 }
}

The rule checks the request.auth.token.roleType field to verify that the user has the correct role type before allowing them to read/write the document.

Example 2 — Let’s assume the scenario involves granting the administrator the ability to modify a document by specifying certain fields, while other users must provide all necessary fields in order to update the document.

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   function isAdminUser() {
       return request.auth.uid in get(/databases/$(database)/documents/workspaces/$(workspaceId)/admins).data.uids;
     }
     
     function isOwner() {
       return request.auth.uid == resource.data.userId;
     }
     
     function hasRequiredFields() {
       return request.resource.data.keys().toSet().hasAll(["required_field_1", "required_field_2", "required_field_3"]);
     }
     
     function hasOverrideFields() {
       return request.resource.data.keys().toSet().hasAny(["status", "updatedAt"]);
     }
   match /workspaces/{workspaceId}/members/{memberId}/leaves/{leaveId} {
     allow update: if (isAdminUser() && hasOverrideFields()) || (!isAdminUser() && hasRequiredFields());
   }
 }
}

By enforcing this access control, the rule ensures that only authorized users can modify the documents in the collection and that any changes made are consistent with the required fields. This helps to maintain the integrity and security of the data in the collection.

Field Level validation rules

To verify value belongs to a List of values

Let’s consider a simple example. Suppose you have a workspace collection in your Firestore database, with a subcollection called members. Each member has a role_type field that can be either admin, member, HR or guest.

Now, let’s say you want to allow write access to only those users who have the role of admin, member, or hr. Users without these roles will not be able to update the members subcollection of the workspace document. To do this, you can use a Firestore security rule like this:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /workspaces/{workspaceId}/members/{memberId} {
     allow write: if request.auth != null && request.resource.data.role_type in ['admin', 'member', 'hr'];
   }
 }
}

The rule allows write access to the members subcollection only if the user making the request is authenticated (request.auth != null) and if the role_type field of the requested document is either admin, member, or hr.

Require All Fields for Request

Here’s an example of a security rule that allows create access only if the request contains all the required fields:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /collection/{documentId} {
     allow create: if request.resource.data.keys().hasAll(["required_field_1", "required_field_2", "required_field_3"]);
   }
 }
}

The request.resource.data.keys().hasAll() method checks if the request object contains all the required fields. If it does, the rule allows create access. If it doesn't, the rule denies access

Restrict Update to Specific Fields

Here’s an example of a security rule that restricts a user to update specific fields:

rules_version = '2';
service cloud.firestore {
 match /myCollection/{docId} {
   allow update: if !request.resource.data.diff(resource.data).affectedKeys().hasOnly(["specificField"]);
 }
}

Restrict Create access with Required and Optional Fields

Here’s a security rule that allows a create operation only if the request contains all the required fields and some optional fields:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /collection/{documentId} {
     allow create: if request.resource.data.keys().hasAll(["required_field_1", "required_field_2", "required_field_3"]) &&
                    (request.resource.data.keys().hasAny(["optional_field_1", "optional_field_2"]);
   }
 }
}

Allow only specific fields to be updated

This rule ensures that only the specified fields can be modified, while all other fields remain unchanged.

rules_version = '2';
service cloud.firestore {
 match /myCollection/{documentId} {
   allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(['field1', 'field2']);
 }
}

Time-based access control

Example 1 — Suppose you have a mobile app allowing users to book doctor appointments. You want to ensure that appointments can only be booked during business hours (i.e., between 9 am and 5 pm on weekdays).

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   // Only allow appointment booking during business hours
   match /appointments/{documentId} {
     allow create: if (date(request.time).getDay() >= 1 && date(request.time).getDay() <= 5) && (request.time.hour() >= 9 && request.time.hour() < 17) && request.auth != null;
   }
 }
}

Example 2 — Allow write access to a document only once per day:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /collection_name/{documentId} {
     allow write: if request.time.date() != resource.data.lastUpdated.date() 
                     && request.auth != null;
   }
 }
}

This rule compares the date portion of the request time with the date portion of the document’s lastUpdated field. It allows write access only if the dates are not equal and the user is authenticated.

Data type validation

A field data type validation rule is a security rule that checks whether the data in a specific field of a document has the expected data type.

For example, if a field is supposed to hold a numeric value, but a string value is entered instead, it could cause errors in calculations or database queries. Similarly, if a field is expected to contain a boolean value but a string or number is entered instead, it could cause unexpected behavior.

Here’s the list of all the different data types that you can check:

value is bool
value is string
value is number
value is int
value is float
value is timestamp
value is duration
value is path
value is list
value is map

Here’s an example rule for a member document that allows creation only if the provided data has valid data types:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   match /workspaces/{workspaceId} {
     
     match /members/{memberId} {
       allow create: if (
         request.resource.data.hasAll(['name', 'email', 'role_type', 'joining_date']) &&
         request.resource.data.name is string &&
         request.resource.data.email is string &&
         request.resource.data.role_type is int &&
         request.resource.data.joining_date is timestamp
       );
     }
   }
 }
}

Optional field data type validation

For example, consider a user document that has optional fields for phone number and address. The rule will check if the phone number field, if present, is of number type, and the address field, if present, is also of string type.

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
  function validateOptionalFields(data) {
    return (!('phone_number' in data.keys()) || data.phone_number is number) && 
        (!('address' in data.keys()) || data.address is string);
  }
 
  match /users/{userId} {
    allow write: if validateOptionalFields(request.resource.data);
  }
 }
}

Password complexity

In this example, we require that passwords must:

  • Be at least 8 characters long
  • Contain at least one letter and one number
rules_version = '2';
service firebase.auth {
 match /users/{uid} {
   allow update: if request.auth.uid == uid
               && request.resource.password.matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/);
 }
}

Email Validation Rule

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
   function isValidEmail(email) {
     // regular expression pattern to match valid email format
     const pattern = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/;
     // check if email matches pattern
     return email.matches(pattern);
   }
   
   match /collection/{documentId} {
     allow read, write: if isValidEmail(request.resource.data.email);
   }
 }
}

Conclusion

In conclusion, implementing security rules for your Firebase Firestore is a critical step towards protecting your application and user data. It is important to note that these security rules are not a one-size-fits-all solution and should be adapted to your specific application’s requirements.

Thank you for reading, and happy coding! 👋

Useful Articles


radhika-s image
Radhika saliya
Mobile App Developer | Sharing knowledge of Jetpack Compose & android development


radhika-s image
Radhika saliya
Mobile App Developer | Sharing knowledge of Jetpack Compose & android development

Whether you need...

  • *
    High-performing mobile apps
  • *
    Bulletproof cloud solutions
  • *
    Custom solutions for your business.
Bring us your toughest challenge and we'll show you the path to a sleek solution.
Talk To Our Experts
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.