Role-Based Access Control in Cloud Firestore
Learn how to use Firebase Security Rules to implement role-based Firestore access
I’ll try to explain this with an example so that you can easily understand when to use this method of authentication when building your application.
Role-Based User Authorization: Where users can have many roles and those roles provide different privileges that enable different operations on your (Firestore) database.
To better understand this, let’s take a look at the following data model.
Here in our data model, we have a
users collection with a number of
user documents. The
user document has a field
roles which is an array of strings, where each string represents which role(s) the specific user has , allowing them to perform actions according to the privilege(s) this role has.
There is a slight issue with how the data model is structured: users can edit their own roles, so we need to restructure the data model so only admin users can edit other users’ roles.
Let’s create a
posts collection to separate users’ roles from their data:
Documents in the
posts collection have four main fields, the content (also the image),
published which determines whether or not the post is made public, a
timestamp to give information about when it was published, and
userId to associate the author of the post to that document.
Let’s understand the logic on how this works with rules :
Look at the match block which references the
users collection on line 4. It has custom functions set to it and you’ll learn to implement it later after we understand the logic.
So basically, on the
users collection, we want to allow read if a user is logged in to our app. Updating or deleting a user requires admin privileges. To check if it’s a request from an admin account we use
hasAnyRole(list) which takes in a list of strings that we’ll match to the roles of that user and if a user has any of those roles it will allow the operation.
Now, regarding the
posts collection, to read a post we check if a user is logged in and the post has been published. However, the admin can read any post, no matter it is published or not. So we added a
or admin check, which checks if the requesting user is
admin or not and allows admin to read unpublished posts.
Coming to creating a post we’ll first check if it has all the valid fields and format and the user is the author of the post.
For updating a post, it’s almost a similar process but we only allow certain fields of a document to be updated once after it’s posted. So we’ll use another custom function to validate an update post request, and also allow additional roles to make an update to a post.
Finally, deletion of the post can only be done by admin, so we check if the requesting user is an admin or not. You could optionally allow the author of the post to be able to delete a post but that depends on your use case.
Let’s get to the unimplemented functions from the above logic :
This function is pretty straightforward, it just checks if the requesting user’s auth attribute is
The next function is
This function checks if the user is logged in and then gets the roles array that we created earlier in the user’s data model and checks if it contains any of the roles.
Checking the validity of the post is a bit complex than these as we need to check multiple attributes to approve the request.
post is the variable that holds the data from the request.
isOwner variable holds the boolean value of if a user is the owner of the post.
isNotFromPastOrFuture will check if the timestamp on the incoming data is not from the past or future. This will help you to server-side validate if the user is trying to post something back or forward in time.
hasMandatoryFields variable will check if all the mandatory keys exist in the incoming data. This will help you filter incomplete data sent through unauthorized clients.
And finally returns the boolean of all these variables.
Now, validation of updating post is a bit different from this :
Similar to the
newPost function the
post variable holds the incoming data.
Unlike the new post, we only need to check if there are only keys that can be modified after publishing the post.
isValid variable checks if the content type is a string (depends on what data type you want it to be) and checks if the content length doesn’t exceed 2000 characters.
This was a basic example of role-based authorization. You can customize these rules accordingly to match your purpose.
I hope this article helped you understand writing Firestore rules. If you find anything difficult to understand feel free to leave a comment.