This page builds on the concepts in Structuring Security Rules
and Writing Conditions for Security Rules
to explain how Cloud Firestore
Security Rules
interact with queries. It takes a closer look at how
security rules affect the queries you can write and describes how to ensure your
queries use the same constraints as your security rules. This page also
describes how to write security rules to allow or deny queries based on query
properties like limit
and orderBy
.
Rules are not filters
When writing queries to retrieve documents, keep in mind that security rules are not filters—queries are all or nothing. To save you time and resources, Cloud Firestore evaluates a query against its potential result set instead of the actual field values for all of your documents. If a query could potentially return documents that the client does not have permission to read, the entire request fails.
Queries and security rules
As the examples below demonstrate, you must write your queries to fit the constraints of your security rules.
Secure and query documents based on auth.uid
The following example demonstrates how to write a query to retrieve documents
protected by a security rule. Consider a database that contains a collection of story
documents:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
In addition to the title
and content
fields, each document stores the author
and published
fields to use for access control. These examples assume
the app uses Firebase Authentication
to set the author
field
to the UID of the user who created the document. Firebase
Authentication also populates the request.auth
variable in
the security rules.
The following security rule uses the request.auth
and resource.data
variables to restrict read and write access for each story
to its author:
service
cloud
.
firestore
{
match
/databases/{database
}
/
documents
{
match
/stories/{storyid
}
{
//
Only
the
authenticated
user
who
authored
the
document
can
read
or
write
allow
read,
write
:
if
request
.
auth
!=
null
&&
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
}
}
Suppose that your app includes a page that shows the user a list of story
documents that they authored. You might expect that you could use the following
query to populate this page. However, this query will fail, because it does not
include the same constraints as your security rules:
Invalid : Query constraints do not match security rules constraints
// This query will fail
db
.
collection
(
"stories"
).
get
()
The query fails even if
the current user actually is the author of every story
document. The reason for this behavior is that when Cloud Firestore
applies your security rules, it evaluates the query
against its potential
result set, not against the actual
properties of
documents in your database. If a query could potentially
include documents
that violate your security rules, the query will fail.
In contrast, the following query succeeds, because it includes the same
constraint on the author
field as the security rules:
Valid : Query constraints match security rules constraints
var
user
=
firebase
.
auth
()
.
currentUser
;
db
.
collection
(
"stories"
)
.
where
(
"author"
,
"=="
,
user
.
uid
)
.
get
()
Secure and query documents based on a field
To further demonstrate the interaction between queries and rules, the security
rules below expand read access for the stories
collection to allow any user to
read story
documents where the published
field is set to true
.
service
cloud
.
firestore
{
match
/databases/{database
}
/
documents
{
match
/stories/{storyid
}
{
//
Anyone
can
read
a
published
story
;
only
story
authors
can
read
unpublished
stories
allow
read
:
if
resource
.
data
.
published
==
true
||
(
request
.
auth
!=
null
&&
request
.
auth
.
uid
==
resource
.
data
.
author
);
//
Only
story
authors
can
write
allow
write
:
if
request
.
auth
!=
null
&&
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
}
}
The query for published pages must include the same constraints as the security rules:
db.collection("stories").where("published", "==", true).get()
The query constraint .where("published", "==", true)
guarantees that resource.data.published
is true
for any result. Therefore, this query
satisfies the security rules and is allowed to read data.
OR
queries
When evaluating a logical OR
query ( or
, in
, or array-contains-any
)
against a ruleset, Cloud Firestore
evaluates each comparison value
separately. Each comparison value must meet the security rule constraints. For
example, for the
following rule:
match
/
mydocuments
/
{
doc
}
{
allow
read
:
if
resource
.
data
.
x
>
5
;
}
Invalid
: Query does not guarantee that x > 5
for all potential documents
// These queries will fail
query
(
db
.
collection
(
"mydocuments"
),
or
(
where
(
"x"
,
"=="
,
1
),
where
(
"x"
,
"=="
,
6
)
)
)
query
(
db
.
collection
(
"mydocuments"
),
where
(
"x"
,
"in"
,
[
1
,
3
,
6
,
42
,
99
])
)
Valid
: Query guarantees that x > 5
for all potential documents
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
Evaluating constraints on queries
Your security rules can also accept or deny queries based on their constraints.
The request.query
variable contains the limit
, offset
,
and orderBy
properties of a query. For example, your security rules
can deny any query that doesn't limit the maximum number of documents
retrieved to a certain range:
allow
list
:
if
request
.
query
.
limit
< =
10
;
The following ruleset demonstrates how to write security rules that evaluate
constraints placed on queries. This example expands the previous stories
ruleset with the following changes:
- The ruleset separates the read rule into rules for
get
andlist
. - The
get
rule restricts retrieval of single documents to public documents or documents the user authored. - The
list
rule applies the same restrictions asget
but for queries. It also checks the query limit, then denies any query without a limit or with a limit greater than 10. - The ruleset defines an
authorOrPublished()
function to avoid code duplication.
service
cloud
.
firestore
{
match
/
databases
/
{
database
}
/
documents
{
match
/
stories
/
{
storyid
}
{
//
Returns
`true`
if
the
requested
story
is
'published'
//
or
the
user
authored
the
story
function
authorOrPublished
()
{
return
resource
.
data
.
published
==
true
||
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
//
Deny
any
query
not
limited
to
10
or
fewer
documents
//
Anyone
can
query
published
stories
//
Authors
can
query
their
unpublished
stories
allow
list
:
if
request
.
query
.
limit
< =
10
&&
authorOrPublished
();
//
Anyone
can
retrieve
a
published
story
//
Only
a
story
's author can retrieve an unpublished story
allow get: if authorOrPublished();
// Only a story'
s
author
can
write
to
a
story
allow
write
:
if
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
}
}
Collection group queries and security rules
By default, queries are scoped to a single collection and they retrieve results only from that collection. With collection group queries , you can retrieve results from a collection group consisting of all collections with the same ID. This section describes how to secure your collection group queries using security rules.
Secure and query documents based on collection groups
In your security rules, you must explicitly allow collection group queries by writing a rule for the collection group:
- Make sure
rules_version = '2';
is the first line of your ruleset. Collection group queries require the new recursive wildcard{name=**}
behavior of security rules version 2. - Write a rule for you collection group using
match /{path=**}/ [COLLECTION_ID] /{doc}
.
For example, consider a forum organized into forum
documents containing posts
subcollections:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
In this application, we make posts editable by their owners and readable by authenticated users:
service
cloud
.
firestore
{
match
/databases/{database
}
/
documents
{
match
/forums/{forumid
}
/
posts
/
{
post
}
{
//
Only
authenticated
users
can
read
allow
read
:
if
request
.
auth
!=
null
;
//
Only
the
post
author
can
write
allow
write
:
if
request
.
auth
!=
null
&&
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
}
}
Any authenticated user can retrieve the posts of any single forum:
db.collection("forums/technology/posts").get()
But what if you want to show the current user their posts across all forums?
You can use a collection group query
to retrieve
results from all posts
collections:
var
user
=
firebase
.
auth
()
.
currentUser
;
db
.
collectionGroup
(
"posts"
)
.
where
(
"author"
,
"=="
,
user
.
uid
)
.
get
()
In your security rules, you must allow this query by
writing a read or list rule for the posts
collection group:
rules_version
=
'2'
;
service
cloud
.
firestore
{
match
/databases/{database
}
/
documents
{
//
Authenticated
users
can
query
the
posts
collection
group
//
Applies
to
collection
queries,
collection
group
queries,
and
//
single
document
retrievals
match
/{path=**
}
/
posts
/
{
post
}
{
allow
read
:
if
request
.
auth
!=
null
;
}
match
/
forums
/
{
forumid
}
/
posts
/
{
postid
}
{
//
Only
a
post's
author
can
write
to
a
post
allow
write
:
if
request
.
auth
!=
null
&&
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
}
}
Note, however, that these rules will apply to all collections with ID posts
,
regardless of hierarchy. For example, these rules apply to all of the following posts
collections:
-
/posts/{postid}
-
/forums/{forumid}/posts/{postid}
-
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
Secure collection group queries based on a field
Like single-collection queries, collection group queries must also meet the
constraints set by your security rules. For example, we can add a published
field to each forum post like we did in the stories
example above:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
We can then write rules for the posts
collection group based on the published
status and the post author
:
rules_version
=
'2'
;
service
cloud
.
firestore
{
match
/
databases
/
{
database
}
/
documents
{
//
Returns
`true`
if
the
requested
post
is
'published'
//
or
the
user
authored
the
post
function
authorOrPublished
()
{
return
resource
.
data
.
published
==
true
||
request
.
auth
.
uid
==
resource
.
data
.
author
;
}
match
/
{
path
=**
}
/
posts
/
{
post
}
{
//
Anyone
can
query
published
posts
//
Authors
can
query
their
unpublished
posts
allow
list
:
if
authorOrPublished
();
//
Anyone
can
retrieve
a
published
post
//
Authors
can
retrieve
an
unpublished
post
allow
get
:
if
authorOrPublished
();
}
match
/
forums
/
{
forumid
}
/
posts
/
{
postid
}
{
//
Only
a
post
's author can write to a post
allow write: if request.auth.uid == resource.data.author;
}
}
}
With these rules, Web, Apple, and Android clients can make the following queries:
-
Anyone can retrieve published posts in a forum:
db.collection("forums/technology/posts").where('published', '==', true).get()
-
Anyone can retrieve an author's published posts across all forums:
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
-
Authors can retrieve all their published and unpublished posts across all forums:
var user = firebase . auth () . currentUser ; db . collectionGroup ( "posts" ) . where ( "author" , "==" , user . uid ) . get ()
Secure and query documents based on collection group and document path
In some cases, you might want to restrict collection group queries based on document path. To create these restrictions, you can use the same techniques for securing and querying documents based on a field.
Consider an application that keeps track of each user's transactions among several stock and cryptocurrency exchanges:
/users/{userid}/exchange/{exchangeid}/transactions/{transaction}
{
amount: 100,
exchange: 'some_exchange_name',
timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
user: "some_auth_id",
}
Notice the user
field. Even though we know which user owns a transaction
document from the document's path, we duplicate this information in each transaction
document because it allows us to do two things:
-
Write collection group queries that are restricted to documents that include a specific
/users/{userid}
in their document path. For example:var user = firebase . auth () . currentUser ; // Return current user 's last five transactions across all exchanges db . collectionGroup ( "transactions" ) . where ( "user" , "==" , user ) . orderBy ( 'timestamp' ) . limit ( 5 )
-
Enforce this restriction for all queries on the
transactions
collection group so one user cannot retrieve another user'stransaction
documents.
We enforce this restriction in our security rules and include data validation
for the user
field:
rules_version
=
'2'
;
service
cloud
.
firestore
{
match
/databases/{database
}
/
documents
{
match
/{path=**
}
/
transactions
/
{
transaction
}
{
//
Authenticated
users
can
retrieve
only
their
own
transactions
allow
read
:
if
resource
.
data
.
user
==
request
.
auth
.
uid
;
}
match
/
users
/
{
userid
}
/
exchange
/
{
exchangeid
}
/
transactions
/
{
transaction
}
{
//
Authenticated
users
can
write
to
their
own
transactions
subcollections
//
Writes
must
populate
the
user
field
with
the
correct
auth
id
allow
write
:
if
userid
==
request
.
auth
.
uid
&&
request
.
data
.
user
==
request
.
auth
.
uid
}
}
}
Next steps
- For a more detailed example of role-based access control, see Securing Data Access for Users and Groups .
- Read the security rules reference .