Firebase Security rules : Everything you need to know to get started

Rutvik Tak
Level Up Coding
Published in
8 min readJan 18, 2022

--

Imagine a scenario where a malicious user is able to read or edit the parts of your database which he wasn’t supposed to 😬 like private data of other users, credit card details, contacts and is even able to delete the data!

Coze, someone forgot to secure the database!😶‍🌫️

Only if we knew about the Firebase Security rules which could have prevented this. 🥲
Let’s make sure we have that covered now.👮‍♂️

Table Of Content:

  1. What are Firebase Security rules?
  2. Security rules language
  3. Setup Security rules
  4. Structure & Syntax
  5. request & resource
  6. Functions
  7. Helper methods
  8. Write better & secure rules
  9. Test/Validate rules
  10. Deploy

1. What are Firebase Security rules?

Firebase Security rules stand between your data and the users. They decide who is allowed to get access to your data stored in RTDB, Firestore and Storage.
Every request for every CRUD operation on your data goes through them and they make final decision if that request should go through or not.

2. Security rules language:

The way you define rules depends on which Firebase product you’re using. According to the official docs :

  • RealTime Database(RTDB): Security rules for this uses a syntax that looks like JavaScript in a JSON structure.
  • Cloud Firestore and Storage: For these two, Security rules use a language based on the Common Expression Language (CEL) that builds on CEL with match and allow statements that support conditionally granted access.

For the scope of this article, we will be focusing on how to write rules for Cloud Firestore and Storage. The general idea of how they work remains the same for all the products, only thing that changes is the structure & syntax in which you write them.

3. Setup Security rules:

Security rules reside outside of your app. You can set them up directly through the Firebase Console or through the Firebase CLI.

  • Using Firebase Console :
    In your Firebase console, open the Firebase Database tab, there in the upper hand side you can choose the rules tab. You can write and publish the rules from here.
  • Using Firebase CLI :
    If you have the firebase project initialized locally with Firestore and Storage through Firebase CLI then you would have two rules files created, one for Firestore and other for Storage rules. You can write the rules through here as well.

As a good developer 😊, make sure to update your rules from a single place, else you might find yourself in conflicting updates over the rules.

4. Structure & Syntax :

Security rules for Firestore and Storage use the following structure and syntax:

  • name : Name of the service you want to define rules for( cloud.firestore or firebase.storage )
  • path : Path of the document in the Database or the path of the folder/file in the Storage.
  • methods : Methods you’re allowing to run. Standard methods are get, list, create, update, delete.
    We also have read and write where read is an alias for get, list and write for create, update, delete.
  • condition : Based on the result of the condition, the request is either allowed or denied. It should return a boolean.

match :

Every match block declares a path pattern associated with it. Whenever a request comes in, every match blocks path is matched against the path for the required operation( request.path ).

Matches against paths can be complete or partial:

  • Partial matches : This is when only the prefix of the path is matched.
  • Complete matches : When the request path matches completely with the path of the match block.

When a complete match is made the rules within the block are evaluated. When a partial match is made the nested match blocks are tested to see whether any nested path will complete the match.

In the below example, given request.path == /example/hello/nested/path the following declarations indicate whether they are a partial or complete match and the value of any variables visible within the scope.(example src)

  • Single-wildcard: This variable represents the document Id for the document at that respective collection .It is wrapped within a curly braces declared as : {variable} .
  • Recursive-wildcard: This represent the path segments at and below the path and declared as : {variable=**} .

Rules are not evaluated unless a complete match is made.

allow :

This are your actual rules. allow statements can grant permissions for more than one method. And the match block can have one or more allow statements.

5. request & resource :

  • request: It includes, the authentication( request.auth) info generated from Firebase Authentication. Along with that, it might also include the incoming data(request.resource.data) that the user is trying to write or update on a document.
    You can check out the other values it holds here.
  • resource: It represents the existing document that you’re trying to write to and the document data is accessed through resource.data .

In the below example, before allowing the request for updating a users post, we are first checking if the userId in the auth is same as the userId in the post document.

request.resource != resource The prior represents incoming data while the latter represents the existing data on a document.

6. Functions :

Once you start writing rules, your rules can easily get less readable as your conditions grow and be repeated over all the places. There are also chances that you will forget to update a particular condition which is used at multiple places. Not unusual for devs!😶
To overcome this,…..

Functions! Rules support writing functions that can be used to improve upon readability and deduplication of code.

In the below example, we extracted the conditions for checking if the user is authenticated or not and if he’s the owner of the doc in two different functions called isSignedIn and isOwner.

This makes it easy to reuse this conditions anywhere else if needed.

7. Helper methods :

When you’re writing Security rules for the Firestore, you also have access to this helper methods.

  • exists : Checks if the document exists or not at the provided path and returns the boolean.
    Below, we are creating the user document only if it does not exists already.
  • get : Returns the document at the provided path . If not present, null is returned.
    Below, we are checking if the one who’s requesting an update on the post is admin or not, by fetching that user document and checking the role.
  • existsAfter : Checks if a document exists or not at given path, assuming the current request succeeds. Equivalent to getAfter(path) != null .
  • getAfter : Returns the created document at provided path, assuming the current request succeeds.

existsAfter and getAfter can be useful if you want to validate the documents that are parts of batch writes or transactions.

Remember that any time your rules include a read, like the rules above, you’re billed for a read operation in Cloud Firestore.

8. Write Better & Secure rules :

  • Overlapping matches :
    As we discussed, that all the match blocks are checked whenever a request comes in. And if there are multiple matches, then final results is based on the OR of results from each match block.

Here, all the requests for accessing cities collection will be granted coze even though the first match block denies it but the second one is always true.

Make sure you don’t have such overlapping matches.

  • Overriding allow statements :
    If we have multiple allow statements for the same action, and if any one of them grants access then the request is allowed.

Even though the create action is denied but the write which includes create is allowed. That’s why the request is allowed.

Be careful of such overriding allow statements.

  • Use Functions : Extract your conditions in separate functions whenever possible. This not only helps in better readability but also less duplication of code.
    On top of this, you’ll be using request.resource and resource in your rules a lot, it’s easy to mistake one for the other. To avoid this, use two separate functions that return them.

9. Test/Validate rules :

So you learned how to write rules! Very cool!😎
Now, let’s test them to make sure they work as expected!

You can either test them through Firebase Emulator or the Rules Playground in Firebase Console.

  • Firebase Emulator : If you plan to use Emulator then follow this guide for setting up Emulator. When you run the Emulator, your rules set up in the firestore.rules and storage.rules if present will be loaded.
    Then, once you configure your app to use the Emulator ,you’re good to go and test them.
  • Rules Playground : If you want to quickly test the rules without the hassle of setting up the emulator, you can use the Rules Playground.

10. Deploy :

  • Using Firebase CLI : To deploy rules through CLI ,use the following commands.
  • Using Firebase Console : Just as you edit the rules, you can publish from right there.

That! was a lot for just getting started! But, look who just learned a ton of good stuff and is now ready to secure the data like a Senior Dev!😎

There’s still some stuff about data validation which isn’t covered here which will give you more granular control over what gets added and removed from your database. We’ll be covering that in depth in upcoming articles.

Untill then, have an amazing day! And thanx for reading the article!🙏

👏 Enjoyed the article! Would appreciate the clap :)
🚀 More such articles coming where we explore flutter and other interesting stuff in app development.
👋 Follow up for upcoming articles if you enjoyed reading and learning from what I write.
💬 Let me know what you liked and what you would like to read in upcoming articles.

--

--