Announcing cross-service Security Rules

We’re excited to deliver one of the most popular feature requests for Security Rules. Security Rules in Cloud Storage for Firebase now supports cross-service Rules with two brand new functions firestore.get() and firestore.exists(). These functions let you query your projects’ Firestore data, similar to the get() and exists() functions in Firestore Rules. This new functionality allows developers to build flexible permissions without repeating logic in both Firestore and Storage. We have seen developers implementing things like group membership checks with complicated workarounds using Cloud Functions or other servers to modify storage URLs, etc. Cross-service Rules replaces all those workarounds with an easy implementation.

Cross service Rules in Action

Let’s see how cross-service Rules can change the way you build your app. Say you have a social media app, and you want to allow users to share photos with a set of friends. Your Firestore database stores data of all your users in a collection called users, with a document for each user’s UID. Within each document (representing a user), there’s a list called friends that contains other UIDs. The photos for the main application are stored in Cloud Storage for Firebase, with each user having a folder named for their UID. To only allow each user to view the pictures of their friends, you can add rules like:

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{uid}/files/{fileId} {
      // Owners can view and update their own files if they're listed in Firestore
      allow write, read: if
          request.auth.uid == uid &&
          firestore.exists(/databases/(default)/documents/users/$(uid));

      // Friends can read if they are listed
      allow read: if
          request.auth.uid in          
          firestore.get(/databases/(default)/documents/users/$(uid)).data.friends;
    }
  }
}

Notice that the Firestore path names need to be fully specified for each of these functions, meaning they must contain the database name as well as the collection name. For example, you must write firestore.get(/databases/(default)/documents/foo/bar) instead of firestore.get(/foo/bar). This feature allows you to read only the Firestore data belonging to the same Firebase project as your Cloud Storage; you will not be able to query Firestore data from a different project.

We do not support getAfter() and existsAfter() function variants, as those are specifically for Firestore transactions and queries that mutate Firestore.

Enabling the feature

First, you will need to set up both Firestore and Cloud Storage for Firebase for your project. To enable the feature, the Cloud Storage for Firebase service account will need permissions to read your project’s Firestore documents.

In the Firebase Console, the first time you use the new Firestore methods (firestore.get() or firestore.exists()) in your Security Rules and hit Publish, you will be prompted to add the permissions:

Provision cross-service rules permissions
Provision cross-service rules permissions

If you are using the Firebase CLI, make sure you’ve updated past CLI version 11.10.0. The first time you deploy rules containing the new functions, you will see a similar prompt for adding the permissions:

Firebase CLI permissions
Firebase CLI permissions

Attach the permissions and you are all set! It’s that simple!

Performance

To avoid adding significant latency to rules evaluation, make sure your Firestore and Storage resources are located close by or in the same location. Requests for Storage resources located in the same region as the Firestore resources may see a latency increase of ~75ms for each Firestore read. If requests had to travel between distant locations, the latency could be higher; for example, for an even distribution of regions around the globe, we estimate an average latency increase of ~200ms. If your rules do not use any of these function calls, you should see no latency impact, even if the feature is enabled for your project.

Firestore documents are also cached to avoid duplicate reads on accessing the same Firestore document multiple times, within a single request. This means your rules may include multiple firestore.get() and firestore.exists() calls to examine different attributes of a single document, with no additional latency impact or reads.

Billing & Quota

Both firestore.get() and firestore.exists() calls result in a read operation on your Firestore database. You will be billed for the Firestore reads, even if your rules reject the request. See Cloud Firestore Pricing for more specific billing information.

As mentioned before, the additional Firestore accesses will naturally add to the latencies on Storage requests. To limit this impact, we enforce a limit of two Firestore document queries per rules evaluation. Querying more than two unique documents will result in rules denying access and failed requests. You can however do multiple function calls on the same document. These do not count towards the limit, as they will be cached. If you find that you need to query more than two documents in your rules, you may consider pre-processing your data in Firestore. In case that still doesn’t work well for your use case, please contact our Support and we will be happy to work with you.

Testing

Emulator Suite

If you’re not testing your Security Rules yet, check out this documentation to get started and this blog post on adding your tests to CI.

The new Security Rules functions are supported in the Cloud Storage for Firebase emulator version v11.10.0. This is the easiest way to test them out. To get started, run the emulators for both Firestore and Cloud Storage and add rules containing the new cross-service functions in your Storage rules. No additional setup is needed. The Storage and Firestore emulators will automatically work together. You can then query your Storage emulator or write tests against it for verifying your Rules.

Rules Playground

The Rules Playground in the Firebase Console also supports the new functions. You can use this to get a quick feel for how the new functions behave. To test using the Rules Playground, navigate to the Rules Playground from the Rules Editor for Storage and simply write a few rules with the new functions. The rules verification will run against your project’s Firestore data and display the results.

Manual verification is great for testing the feature out in development. For production,we highly recommend using the emulator suite to add comprehensive tests for testing all the rules functionality.

So there you have it. That’s our shiny new feature. Try it out and let us know what you think. For the next milestone, we plan to support cross-service rules queries to the Realtime database! Stay tuned!