While experimenting with Firebase and specifically Cloud Firestore I reached the point where I had to control who can read and write data to my collections. Since the application I am building is a client-side application only, the Firebase way to restrict access to it is through Firebase Authentication and Cloud Firestore Security Rules.
For the purposes of this blog post, let’s assume that I have 1 main collection called flats
which includes a subcollection called items
.
After reading through the documentation, testing some rules, updating my collections structure, I ended up with the following rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /flats/{flatId} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth != null
&& request.auth.uid in resource.data.idsOfUsersWithAccess;
match /items/{itemId} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth != null
&& request.auth.uid in resource.data.idsOfFlatmatesThatShareThis;
}
}
}
}
What the above rules basically say is that any authenticated user (request.auth != null
) is allow
ed to create
a flat, but they are only allow
ed to read
, update
or delete
a flat if their unique ID (request.auth.uid
) is included in the flat’s property called idsOfUsersWithAccess
.
Now, since the items
collection is a subcollection of the flats
one, I created a nested rule which extends the previous one (read more about rules for hierarchical data in the docs). The rule says that only authenticated users are allow
ed to create
items but in order to read
, update
or delete
an item the user’s unique ID needs to be included in the idsOfFlatmatesThatShareThis
property of the flat.
For some reason however I could not fetch the items of the flat in my app and I was getting the following error:
Uncaught (in promise) FirebaseError: Missing or insufficient permissions.
After quite a bit of digging into my code and the Firebase docs I found the problem. My code to fetch the items of a flat is the following:
firestore.collection('flats').doc(flat.id)
.collection('items').get()
My simple brain (I like to call him Brian) had this thought:
If the authenticated user queries all flat items, the result will surely only include the ones they have access too, based on the security rules that I created.
WRONG.
The documentation on writing conditions for Cloud Firestore Security Rules clearly says:
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.
Oopsie!
The problem was not in my rules. It was in my query. Adding a where
clause to match the rule fixed the problem and I could now load the intented items:
firestore.collection('flats').doc(flat.id)
.collection('items')
.where('idsOfFlatmatesThatShareThis', 'array-contains', state.user.id)
.get()
What did I learn?
To spend a bit more time reading the docs before diving into coding! Or at least skimming through all of them quickly ;)
PS: I would totally recommend the video series of Get to know Cloud Firestore on Youtube.
Top comments (0)