This guide builds on the
structuring security rules
guide
to show how to add conditions to your Cloud Firestore Security Rules. If you are not
familiar with the basics of Cloud Firestore Security Rules, see the
getting started
guide.
The primary building block of Cloud Firestore Security Rules is the condition. A
condition is a boolean expression that determines whether a particular operation
should be allowed or denied. Use security rules to write conditions that
check user authentication, validate incoming data, or even access other parts of
your database.
Authentication
One of the most common security rule patterns is controlling access based on the
user's authentication state. For example, your app may want to allow only
signed-in users to write data:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to access documents in the "cities" collection
// only if they are authenticated.
match /cities/{city} {
allow read, write: if request.auth != null;
}
}
}
Another common pattern is to make sure users can only read and write their own
data:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
If your app uses Firebase Authentication or
Google Cloud Identity Platform
, the
request.auth
variable contains
the authentication information for the client requesting data.
For more information about
request.auth
, see
the reference
documentation
.
Data validation
Many apps store access control information as fields on documents in the database.
Cloud Firestore Security Rules can dynamically allow or deny access based on document
data:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to read data if the document has the 'visibility'
// field set to 'public'
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
}
}
The
resource
variable refers to the requested document, and
resource.data
is
a map of all of the fields and values stored in the document. For more
information on the
resource
variable, see
the reference
documentation
.
When writing data, you may want to compare incoming data to existing data.
In this case, if your ruleset allows the pending write, the
request.resource
variable contains the future state of the document. For
update
operations that only
modify a subset of the document fields, the
request.resource
variable will
contain the pending document state after the operation. You can check the field
values in
request.resource
to prevent unwanted or inconsistent data updates:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /cities/{city} {
allow update: if request.resource.data.population > 0
&& request.resource.data.name == resource.data.name;
}
}
}
Access other documents
Using the
get()
and
exists()
functions, your security rules can evaluate
incoming requests against other documents in the database. The
get()
and
exists()
functions both expect fully specified document paths. When using
variables to construct paths for
get()
and
exists()
, you need to explicitly
escape variables using the
$(variable)
syntax.
In the example below, the
database
variable is captured by the match
statement
match /databases/{database}/documents
and used to form the path:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
For writes, you can use the
getAfter()
function to access the state of a
document after a transaction or batch of writes completes but before the
transaction or batch commits. Like
get()
, the
getAfter()
function takes a
fully specified document path. You can use
getAfter()
to define sets of writes
that must take place together as a transaction or batch.
Access call limits
There is a limit on document access calls per rule set evaluation:
- 10 for single-document requests and query requests.
-
20 for multi-document reads, transactions,
and batched writes. The previous limit of 10 also applies to each
operation.
For example, imagine you create a batched write request with 3 write
operations and that your security rules use 2 document access calls to
validate each write. In this case, each write uses 2 of its
10 access calls and the batched write request uses 6 of its 20 access
calls.
Exceeding either limit results in a permission denied error. Some document
access calls may be cached, and cached calls do not count towards the limits.
For a detailed explanation of how these limits affect transactions and
batched writes, see the guide for
securing atomic operations
.
Access calls and pricing
Using these functions executes a read operation in your database,
which means you will be billed for reading documents even if your rules reject
the request. See
Cloud Firestore Pricing
for more specific billing information.
Custom functions
As your security rules become more complex, you may want to wrap sets of
conditions in functions that you can reuse across your ruleset. Security rules
support custom functions. The syntax for custom functions is a bit like JavaScript,
but security rules functions are written in a domain-specific language
that has some important limitations:
- Functions can contain only a single
return
statement. They cannot
contain any additional logic. For example, they cannot execute loops or call
external services.
- Functions can automatically access functions and variables from the scope
in which they are defined. For example, a function defined within
the
service cloud.firestore
scope has access to the
resource
variable
and built-in functions such as
get()
and
exists()
.
- Functions may call other functions but may not recurse. The total call
stack depth is limited to 10.
- In rules version
v2
, functions can define variables using the
let
keyword.
Functions can have up to 10 let bindings, but must end with a return
statement.
A function is defined with the
function
keyword and takes zero or more
arguments. For example, you may want to combine the two types of conditions used
in the examples above into a single function:
service cloud.firestore {
match /databases/{database}/documents {
// True if the user is signed in or the requested data is 'public'
function signedInOrPublic() {
return request.auth.uid != null || resource.data.visibility == 'public';
}
match /cities/{city} {
allow read, write: if signedInOrPublic();
}
match /users/{user} {
allow read, write: if signedInOrPublic();
}
}
}
Using functions in your security rules makes them more maintainable as the
complexity of your rules grows.
Rules are not filters
Once you secure your data and begin to write queries, keep in mind that security
rules are not filters. You cannot write a query for all the documents in a
collection and expect Cloud Firestore to return only the documents that
the current client has permission to access.
For example, take the following security rule:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to read data if the document has the 'visibility'
// field set to 'public'
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
}
}
Denied
: This rule rejects the following query because the result set
can include documents where
visibility
is not
public
:
Web
db.collection("cities").get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
});
Allowed
: This rule allows the following query because the
where("visibility", "==", "public")
clause guarantees that the result set satisfies the rule's condition:
Web
db.collection("cities").where("visibility", "==", "public").get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
});
Cloud Firestore security rules evaluate each query against its potential
result and fails the request if it could return a document that the client does
not have permission to read. Queries must follow the constraints set by
your security rules. For more on security rules and queries, see
securely
querying data
.
Next steps