Firestore Security Rules allow you to restrict access to your Firestore database and perform data validation on writes and reads. If you allow your app to perform read and write operations client-side (which is the case with most Firebase apps), there is nothing that stops people from writing their own code (or tampering with yours) to access your Firestore database.
You should create your security rules as if someone else were using the Firebase SDKs with your keys.
This is a collection of real-world use cases and how they can be implemented using Firestore Security Rules.
Please see the official Firestore docs for a complete reference on how security rules are written and structured.
Operations overview
get
: Get a documentlist
: List collections in a documentcreate
: Create a documentupdate
: Update a documentdelete
: Delete a documentread
: Same asget
andlist
write
: Same ascreate
,update
anddelete
Usage
match /<some_path>/ {
allow create: if <some_condition>;
allow read, update: if <some_condition>;
allow delete: if <some_condition>;
}
Allow all (public access)
This rule will allow everyone to read and write to the path (create and read a
document in the dogs
collection).
match /dogs/{dogs} {
allow read, write: if true;
}
Allow no one (completely private)
No one will be able to read or write to the path. Note that collections are closed for reads and writes by default.
match /dogs/{dog} {
allow read, write: if false;
}
Allow authenticated users
Only authenticated users will be able to write and read documents in the dogs
collection.
match /dogs/{dog} {
allow read, write: if request.auth != null;
}
Users can only read and write their own data
Only authenticated users are allowed to create a document in the users
collection. Reading, updating and deleting is restricted to the user who owns
the document:
match /users/{userId} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth.uid == userId;
}
Only the document owner is allowed to write and read documents in the dogs
collection. This also restricts users from creating a dog
under another user's
ID:
match /users/{userId}/dogs/{dog} {
allow read, write: if request.auth.uid == userId;
}
Instead of storing the user's ID in the document path, you can store it on the
document itself. In the following example, the document contains the owner's
user ID (resource.data.uid
), and only the owner is allowed to read it. To
create a document, the new resource must contain a uid
that matches the
authenticated user's ID:
match /dogs/{dog} {
allow read: if request.auth.uid == resource.data.uid;
allow create: if request.auth.uid == request.resource.data.uid;
}
Allow multiple users (an arbitrary group of users)
The document contains an array of user IDs (resource.data.contributors
). Only
users who have their ID listed in this array are allowed to read the path.
match /dogs/{dog} {
allow read: if request.auth.uid in resource.data.contributors;
}
Role-base access (ACL)
Instead of adding the user ID to the document itself, you can query another
Firestore document to see if the user has the required role. In this example,
users can only delete a document in the articles
collection if they have the
"admin" role (this role is "global", not per-document). Furthermore, users can
only comment on an article if they're not blacklisted, i.e. if their user ID
doesn't exist in the blacklist
collection.
match /articles/{articleId} {
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "admin";
match /comments/{commentId} {
allow create: if exists(/databases/$(database)/documents/blacklist/$(request.auth.uid));
}
}
Alternatively, each document can specify a list of users and their roles. This security rule checks if the authenticated user has the required role to create the specific document.
match /articles/{articleId} {
allow create: if request.resource.data.roles[request.auth.uid] == 'owner';
}
To use the above security rule, the document must contain a map of users and roles, like this:
{
title: "Hello, world!",
body: "This is the article body ...",
roles: {
uid1: "owner",
uid2: "reader",
uid3: "reader",
// ...
}
}
Require a verified email address
Users must have a verified email address to read or write to the dogs
collection.
match /dogs/{dog} {
allow write, read: if request.auth.token.email_verified;
}
Require a specific sign-in provider
Only users who have signed in with a phone number can read the document:
match /dogs/{dog} {
allow read: if request.auth.token.firebase.sign_in_provider == "phone";
}
Refer to the available sign-in providers for more options.
Only allow specific fields
The document can only contain the fields name
and description
. If a user
tries to provide other fields when creating a document, the write is denied.
match /dogs/{dog} {
allow create: if request.resource.data.keys().hasOnly(["name", "description"]);
}
Note that request.resource.data
represents the document after the write
operation has succeeded (i.e. the "future" document). So if this were an
update operation, and if the existing document contained another field like
createdAt
, the above rule would fail because request.resource.data
would
contain name
, description
and createdAt
. See below for how to deal with
updates.
Only allow updating specific fields
Only allow updating the name
and description
field.
match /dogs/{dog} {
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["name", "description"]);
}
In other words: If we compare the new resource (request.resource.data
) with
the existing resource (resource.data
), only name
and description
are
allowed to be different (i.e. changed).
Deny updating or writing to a field
As an alternative to the above, this rule will deny writes if a specific filed
(isPromoted
) field is changed.
match /dogs/{dog} {
allow update: if request.resource.data.isPromoted == resource.data.isPromoted;
}
Even though we check if the incoming data is equal to the existing data, the
client doesn't need to actually send the isPromoted
field; remember that
request.resource.data
represents the resource after a successful write
operation (the "future" document), so if isPromoted
is present on the existing
document, it will be present in request.resource.data
as well.
But what if isPromoted
isn't present on the existing document? Perhaps a
Firebase Function is supposed to populate it later. If this is the case, the
security rule's reference to request.resource.data.isPromoted
will trigger an
error ("Property name is undefined on object"). This will deny the operation, so
no harm is done, but if you want to avoid the error you can modify the rule and
prepare for the missing field.
Here's a custom function that takes this into consideration:
function fieldNotWrittenByUser(field) {
return (
(
(field in request.resource.data)
&& (resource != null && field in resource.data)
&& request.resource.data[field] == resource.data[field]
)
|| (
!(field in request.resource.data) && (resource == null || !(field in resource.data))
)
)
}
// Usage:
match /dogs/{dog} {
allow update: if fieldNotWrittenByUser("isPromoted");
}
(Thanks to Johnny Oshika for pointing out an error in a previous version of the function.)
Explanation:
- If the field is present in the future document (i.e. the document after it's saved), it must be identical to the existing field's value (for example, the field can't be removed).
- If the field is missing from the future document, the operation is only allowed if there isn't an existing document (the user is allowed to create a new document without the field present) -- or if there is an existing document, but it doesn't contain the field.
- The function can be used for both
update
andcreate
operations. It works withset()
(both when setting and omitting the field),update()
, and when attempting to delete the field withfieldName: firebase.firestore.FieldValue.delete()
.
Even though the above function works on both update
and create
operations,
it may be simpler to separate them:
function onlyChangesFields(fields) {
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(fields);
}
function onlyCreatesFields(fields) {
return request.resource.data.keys().hasOnly(fields);
}
// Usage:
// allow create: if onlyCreatesFields(["uid", "name", "age"]);
// allow update: if onlyChangesFields(["name", "age"]);
If you'd like to have a function that works for both update
and create
operations, and one that's less convoluted than the function
fieldNotWrittenByUser()
above, here's another option:
function isCreating() {
return resource == null;
}
function onlyAffectsFields(fields) {
return isCreating()
? request.resource.data.keys().hasOnly(fields)
: request.resource.data.diff(resource.data).affectedKeys().hasOnly(fields)
}
Require document fields
Require the fields name
and description
.
match /dogs/{dog} {
allow write: if request.resource.data.keys().hasAll(["name", "description"]);
}
Validate data types (string, timestamp, number ...)
Make sure the user sends the correct data types.
match /dogs/{dog} {
allow write: if request.resource.data.name is string
&& request.resource.data.age is number
&& request.resource.data.createdAt is timestamp
&& request.resource.data.isHungry is boolean;
}
Timestamp equals current time (server timestamp)
Check if the field createdAt
is a server timestamp; if your client app sends
the createdAt
field, make sure it was created using firebase.firestore.FieldValue.serverTimestamp()
.
match /dogs/{dog} {
allow write: if request.resource.data.createdAt == request.time;
}
Maximum/minumum number of characters
The dog's name
can be no longer than 50 characters.
match /dogs/{dog} {
allow write: if request.resource.data.name is string
&& request.resource.data.name.size() < 50
}
Check if another document exists
Only allow a document to be created if another document exists (in this case, a
document in the users
collection with an ID equal to the authenticated
user's).
match /dogs/{dog} {
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid));
}
Check if another document has a value
Users can only create a document in the articles
collection if they have a user
document with the admin
field set to true
.
match /articles/{articleId} {
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
Custom functions
You can organize rules and logic in custom functions. This prevents you from having to duplicate knowledge, and helps you create a more readable set of rules.
While not production ready, the following example shows some possibilities:
function isSignedIn() {
return request.auth != null;
}
function isResourceOwner(rsc) {
// Check if a resource is owned by the authenticated user.
return isSignedIn() && getOwnerUid(rsc) == request.auth.uid;
}
function getOwnerUid(rsc) {
// Get the owner (user ID) of the resource
return rsc.data.owner;
}
function isAdmin() {
return get(/databases/$(database)/documents/user/$(request.auth.uid)).data.isAdmin == true;
}
function isFutureDate(date) {
return date is timestamp && date > request.time;
}
function incomingData() {
return request.resource.data;
}
function existingData() {
return resource.data;
}
// Example usage:
match /articles/{articleId} {
allow update: if isResourceOwner(resource)
&& incomingData().keys().hasOnly(["title", "body"]);
allow delete: if isAdmin();
match /comments/{commentId} {
// Allow adding a comment if the user owns the parent article
allow create: if isResourceOwner(get(/databases/$(database)/documents/articles/$(articleId)))
}
}
Notes
- The Admin SDK bypasses Firestore security rules. If you have complex requirements, consider doing validations in a Cloud Function.
- You can create automatic test for your security rules.
Closing remarks
I'll try to keep these examples updated, but please give a heads-up in the comments if you find a mistake!