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.
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.
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;
}
}
}
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.
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.
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.
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.
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
.
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
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"]);
}
}
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"]);
}
}
}
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']);
}
}
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.
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
);
}
}
}
}
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);
}
}
}
In this example, we require that passwords must:
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,}$/);
}
}
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);
}
}
}
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! 👋
Whether you need...