Firebase Security Rules gate your user's access to and enforce validations for Firestore, Firebase Storage, and the Realtime Database. It's important to code review the Security Rules, just like you code review the application code. Because these rules are written in a domain-specific language, that puts some people in the position of code reviewing something they don't feel like they understand.
If you're finding yourself in that position, don't worry! This post will walk through how to approach reviewing and giving good feedback on Security Rules. The examples will be from Firestore Security Rules, and are mostly applicable to Storage as well. If you're reviewing Realtime Database Security Rules, although these principles apply, the rules use a different language, so the examples will be different.
When you're looking at Security Rules, check first for any top level rules that apply to everything. A document can match multiple rules, and if any rule grants access, access is granted. In the example below, there's a specific rule that only grants authors access to post documents, but the global rule lets anyone on the internet read or write to any place in your database:
post
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if true; } match /posts/{post} { allow read, write: if request.resource.data.authorUID == request.auth.uid ; } } }
It's important to look for the global match statement match /{document=**} throughout the entire rules file, not just at the top. If there are thorough rules but also a global match statement, universal access will still be granted through the global match statement.
match /{document=**}
There are a very small number of valid use cases for global match statements. For example, granting access to admin users:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if isAdmin(); } } }
If you find a global rule that is read, write: if true, you can stop your review and ask them to fix it. If you find any other condition on a global rule, make sure it makes sense for your application.
read, write: if true
The next thing to do is to look at the data that you're storing. What is Personally Identifiable Information (PII) that should only be accessed by that particular user?
Security Rules apply to entire documents, not just fields. You should be able to look at each kind of document that you have and describe which kind of users should be able to read it, create it, update it, and delete it.
If you find documents where it's fine for most of the document to be read by anyone, but a few fields need to remain private, break those fields into their own document; the easiest way to do this is usually to create a subcollection off the existing document.
Now check the Security Rules for each of the documents that you identified as containing PII. The best practice for PII is that it is keyed by the user's ID, and only that user is allowed access:
match /secrets/{uid} { allow create, update: if request.auth.uid == uid; }
This pattern only works if there's a max of one document in the collection for each user. For cases of multiple documents per user, you could create subcollections of a user document:
match /users/{uid}/secrets/{secret} { allow create, update: if request.auth.uid == uid; }
Or you could include the id of the user who should have access as an attribute, and restrict access to that user:
match /secrets/{secret} { allow create, update: if request.auth.uid == request.resource.data.uid; }
Ideally, you would check that each document is as locked down as possible, but if you have limited time, the most important thing to check is that PII lives in separate documents and those PII documents are only accessible to that user.
Just like application changes should come with test changes, so should Security Rules. Security Rules tests run against the Firebase Emulator Suite, and can be included in your CI setup.
If you're not yet testing Security Rules, check out this documentation to get started, this video for an overview of testing, and this blog post and video on adding your tests to CI.
If the Security Rules are fully tested, there should be a lot of tests. For each kind of document, there are usually four different kinds of access: read, create, update, and delete. (These are just the most common permissions to grant; create, update, and delete can be rolled up into write, and read can be broken into get and list.) For each permission, there should be a test of the happy path, granting permission, and a test for each situation that should deny access.
read
create
update
delete
write
get
list
Say I have one rule:
// firestore.rules allow update: if // User is the author resource.data.authorUID == request.auth.uid && // `authorUID` and `createdAt` are unchanged request.resource.data.diff(request.resource.data).unchangedKeys().hasAll([ "authorUID", "createdAt" ]) && // Title must be < 50 characters long request.resource.data.title.size < 50;
To completely test this, I would write tests around granting access and each way that access could be denied:
// test.js const firebase = require("@firebase/rules-unit-testing"); const dbAuthorAuth = firebase.initializeTestApp({ projectId: TEST_FIREBASE_PROJECT_ID, auth: { uid: "author", email: "alice@example.com" } }).firestore(); const dbOtherAuth = firebase.initializeTestApp({ projectId: TEST_FIREBASE_PROJECT_ID, auth: { uid: "other", email: "otto@example.com" } }).firestore(); describe("blog posts", () => { before(async () => { dbAuthorAuth.doc("drafts/12345").set({ authorUID: "author", createdAt: Date.now(), title: "Make an apple", content: "TODO!" }); }); it("can be updated by author if immutable fields are unchanged", async () => { await firebase.assertSucceeds(dbAuthorAuth.doc("drafts/12345").update({ title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by anyone other than the author", async () => { await firebase.assertFails(dbOtherAuth.doc("drafts/12345").update({ title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by author if the author ID is changed", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ authorUID: "New Person" title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated by author if the created date is changed", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ createdAt: Date.now(), title: "Make an app", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); }); it("cannot be updated if the title is over 50 characters", async () => { await firebase.assertFails(authorDb.doc("drafts/12345").update({ title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." })); });
As the reviewer, look through their test cases, and make sure they have tested each kind of document. If they've written good test descriptions, that's the easiest place to understand what the Security Rules are doing. Similarly, if they've written code comments in their rules, that can also be a good entry point to understanding the Security Rules.
Subcollections don't inherit the rules from the parent document, so make sure they're specifically covered in the Security Rules. Look at the subcollections you're storing in Firestore; if all documents should have the same access that a parent document has, then that parent document should use the glob syntax: match /post/{id=**}.
match /post/{id=**}
What's more common is that a subcollection has been broken out into a subcollection because a different person needs to read it, or someone else is allowed to write to it. In that case, check the Security Rules to make sure it has its own match statement. Nesting is only a stylistic difference; whether you nest your match statements or not, make sure this is a match statement for the documents in the subcollections:
// Nested match statements are fine match /post/{postID} { allow read: if ... match /comments/{commentID} { allow read: if ... } } // Unnested match statements are fine match /post/{postID} { allow read: if ... } match /comments/{commentID} { allow read: if ... }
If you're reviewing Realtime Database Security Rules, child nodes inherit from parent nodes, the opposite of the behavior in Firestore.
Security Rules can enforce type and data validations for specific fields in addition to preventing unwanted access. If you know that some documents have required fields, immutable fields, fields that must be a timestamp, or a field that must contain data in a specific range, check that those are enforced in the Security Rules for those documents. For example, in this rule, I'm checking that the immutable fields of authorUID, publishedAt, and url aren't changed by an update, and that the required fields of content, title, and visible are still present:
authorUID
publishedAt
url
content
title
visible
allow update: if // Immutable fields are unchanged request.resource.data.diff(request.resource.data).unchangedKeys().hasAll([ "authorUID", "publishedAt", "url" ]) && // Required fields are present request.resource.data.keys().hasAll([ "content", "title", "visible" ]);
If you use the more granular permissions of create, update, and delete in place of write, make sure that validations apply to both create and update.
Because the Admin SDK authorizes using service account credentials, all requests from the Admin SDK, including Cloud Functions for Firebase, bypass Security Rules. To give a thorough security review for a Firebase app, it's also necessary to look at any other writes that are happening to your backend via the Admin SDK.
For example, if I have great Firestore Security Rules, but a Cloud Function exports data to a storage bucket that has no Security Rules, that would be a terrible way to treat my user's data. The flip side of this is that Cloud Functions can be used in situations where Security Rules don't work, for example, returning individual fields from documents. See this blog post for tips to use Cloud Functions in conjunction with Security Rules.
Posted by the Firebase team
The COVID-19 pandemic of 2020 brought changes and challenges for many businesses. During this time, we saw developers use resilience and ingenuity to adapt their apps and business models to these new circumstances. For GameNexa Studios, an app developer and consultancy based in India, one of the biggest challenges they faced this year was to figure out how to evolve their monetization strategy in the face of declining ad revenue. The GameNexa team needed a data-driven approach to diversify their revenue stream across their portfolio so they turned to Firebase.
With 40 apps and games under their belt serving 5 million monthly users, GameNexa Studios had a well-established monetization strategy, but like many of their peers, it was disrupted by the COVID-19 pandemic. Previously, the company earned most of its income from ads in their free-to-download titles. However, when many of their advertisers slashed their budgets, GameNexa’s ad revenue dropped too.
To offset their losses, GameNexa needed to pivot from a one-size-fits-all strategy to a diversified revenue model. But diversifying revenue doesn’t mean bombarding users with more offers and in-app promotions - that could drive people away. The most effective monetization strategies are tailored to user preferences and behavior. So, GameNexa first used Google Analytics and Firebase Predictions to better understand their users and then grouped them into segments based on common characteristics like language, and predicted future behavior, like their propensity to make an in-app purchase.
After gaining insight into their users, GameNexa used Firebase Remote Config and Firebase A/B Testing to test new ad placement, formats, and different in-app promotions on each segment to find which offer resonated with each group. They also worked on improving their user experience with Firebase Crashlytics and Firebase Performance Monitoring.
As a result of these efforts, GameNexa saw a 2.5x increase in revenue from in-app purchases and they were able to bring their ad revenue back up to pre-COVID levels by doubling ad impressions. In addition, by creating customized in-app purchase packs for different audiences, GameNexa increased conversions by 6x. Inspired by their own success, GameNexa now plans on sharing what they’ve learned about the power of data-driven monetization and personalization with other developers through their app consultancy. Read their full story and get more details on how they used Firebase to grow and diversify their revenue in our new case study.
Today we launched TWO new features for the Firebase Realtime Database in beta.
First, we launched a new region for the Realtime Database, in Belgium, in addition to our existing region in the United States. If you would like to store your customer data in Europe, or if you’d like to reduce latency for customers in this part of the world, you can do so today.
Second, we launched a management API for the Realtime Database. With just a REST request you can list, create, update, delete, disable, and re-enable Realtime Database instances.
Once you upgrade your Firebase project to our pay as you go plan, it can contain many Realtime Database instances. You can use multiple database instances to scale beyond the limits of a single database instance, balance load, optimize performance, and more. You can even have a mix of database instances in the United States and Belgium in a single project.
Using the new API, adding another Realtime Database instance to a project is as simple as running curl. You can specify either us-central1 (United States) or europe-west1 (Belgium) for location-id.
curl
us-central1
europe-west1
location-id
curl -H "Content-Type: application/json" \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -X POST \ https://firebasedatabase.googleapis.com/v1beta/projects/{project-number}/locations/{location-id}/instances\?database_id\={my-new-database-id}
Let’s list the instances in our project to see if the new database shows up:
curl -H "Content-Type: application/json" \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ https://firebasedatabase.googleapis.com/v1beta/projects/{project-number}/locations/{location-id}/instances
We get this response, showing both our default Realtime Database instance (with type DEFAULT_DATABASE) and the new one we just created (type USER_DATABASE)
DEFAULT_DATABASE
USER_DATABASE
{ "instances": [ { "name": "projects/{project-number}/locations/{location-id}/instances/{default-database-id}", "project": "projects/{project-number}", "databaseUrl": "https://{default-database-id}.firebaseio.com", "type": "DEFAULT_DATABASE", "state": "ACTIVE" }, { "name": "projects/{project-number}/locations/{location-id}/instances/{my-new-database-id}", "project": "projects/{project-number}", "databaseUrl": "https://{my-new-database-id}.firebaseio.com", "type": "USER_DATABASE", "state": "ACTIVE" } ] }
You can use similar calls to disable, re-enable, and delete instances. Have a look at the documentation to learn more about the commands.
This API is available now, and we have enabled it on existing Firebase projects that have used the Realtime Database in the past month. If you ever use the Google Cloud console, you’ll see the API listed there as well. The Firebase console will soon let you create databases in Belgium as well.
We’re excited about these big advances for the Realtime Database, and we hope you are too!
Hello Firebase Developers!
Dependency management is probably one of your least favorite aspects when it comes to app development - whether that’s managing dependencies for an app that you’re building or for an open source project that you’re maintaining.
That’s why we’ve been working on improving dependency management and versioning for our Firebase iOS SDKs, and putting the spotlight on our Firebase BoM (“Bill of Materials”) for Android.
For both platforms, our goal is to make it easy to know which SDK versions to pick for the products you want to use without any muss or fuss, and move straight on to developing your app. There are few other really important motivating factors too.
Let’s get into the specifics for each platform.
Previously, each Firebase SDK for each product maintained its own version number - for both the SDK and the CocoaPod itself. Whenever an SDK got updated, both the SDK and CocoaPod version numbers would get updated too.
This approach was helpful in that each product could update independently more easily, but we’ve heard your feedback that it also had some drawbacks:
That’s why we’re moving to a unified versioning model for all our Firebase SDKs moving forward.
Every Firebase product will now use the same unified version number for its iOS SDK. At each release of a major or minor update, this version number will update for all iOS SDKs. That means that you can just select the same Firebase SDK version number across all Firebase products you want to use - they’ll be designed to be compatible with each other. For any patch releases, the affected product will be fully compatible with all other products at the same minor version. You can run `pod update` to install the latest patches for all products.
There are also a few more reasons why we think moving to this unified versioning model will help iOS developers in the long run.
We’ve been making more and more of our iOS SDKs open source, which has been great for community engagement, but contributing to them hasn’t always been easy.
In the previous model, whenever a product had a new release, a new individualized tag was created for that product. When it came to integrating multiple Firebase products together, these individualized tags made it difficult to figure out which product's tags matched with each other - both from the perspective of using Firebase to build an app, or to contribute to the open source libraries.
With the unified versioning model, you can directly map your Firebase SDK integrations to a single tag in GitHub for both CocoaPods and the Swift Package Manager. The new model also makes it easier for contributors to understand our release and versioning strategy, and make contributions going forward.
While our current release strategy still revolves around CocoaPods, the versioning system we’re now using closely mirrors the conventions used in the Swift Package Manager as well.
Getting ready for that versioning system was additional motivation to move to this model. In fact, the Swift Package Manager for Firebase is now in beta if you want to give it a try.
Over in the Android world, we’re leveraging Gradle’s support for Android BoMs (or “Bill of Materials”), which provides a way to manage the versions for all the libraries you use by specifying a single BoM version for your app.
Firebase has its own “Firebase Android BoM” which has been around for a while, but we’re putting it back into the spotlight as it’ll be the recommended way to manage your library dependencies going forward. When using the Firebase BoM, all you need to do is set a single Firebase Android version in your app - the BoM’s version - and the BoM will pull in the versions of the libraries mapped to that specific BoM.
When you use the Firebase BoM, you’ll still need to declare which Firebase libraries you want to use in your app, but you will no longer need to specify which versions of those libraries your app will require. Instead, just specify the BoM version you want to use, and the BoM will manage which library version to use. And all these versions will be cross-compatible with each other!
Here's an example build.gradle file that uses the BoM:
dependencies { // Import the Firebase BoM implementation platform('com.google.firebase:firebase-bom:25.11.0') // When using the BoM, don't specify versions for Firebase dependencies implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-auth-ktx' implementation 'com.google.firebase:firebase-firestore-ktx' }
If necessary, you can override the library version specified in the BoM by including the desired version in the library's dependency declaration. For example:
dependencies { implementation platform('com.google.firebase:firebase-bom:25.11.0') implementation 'com.google.firebase:firebase-analytics:17.5.0' implementation 'com.google.firebase:firebase-auth-ktx' implementation 'com.google.firebase:firebase-firestore-ktx' }
The goal behind using the Firebase BoM, though, is to avoid needing to do this in your project. Each BoM version is curated to include library versions that all play well together.
The improvements mentioned above are a start to further improvements we plan to make in the near future - from better support for the Swift Package Manager, to providing easier getting started journeys in the Firebase Assistant plugin in Android Studio.
Please stay tuned for more, and as always, reach out to us on the GitHub issue trackers for the Firebase iOS SDK and the Firebase Android SDK to let us know what you think about these updates.
It’s hard to believe it’s already been a year since we launched App Distribution to help you with your internal testing on Android and iOS! Based on all your valuable feedback, we’re excited to share a few updates we made to App Distribution since launch.
Most customers automate their releases with App Distribution to quickly send their latest feature branch builds to their QA teams or release candidate builds to their company. This typically means distributing daily or even multiple times per day, which quickly becomes a long list of releases!
To help make this list more manageable, we added release search in the Firebase App Distribution console and tester apps. Now, it’s easy to search by version or release notes to find, manage, and test the exact release you’re looking to get feedback on.
In-app build alerts notify iOS testers when new builds are available to them, making it easier for testers to install the update right from your test app. In-app build alerts are powered through our lightweight open sourced SDK, which you can quickly integrate into your test app.
To increase download speeds for testers around the world using different devices and network connections, we made improvements to our backend systems that handle uploads and downloads. Now testers have a more reliable experience, with some testers experiencing up to 10 times faster downloads.
We recently released version 2.0 of our fastlane plugin, a major rewrite that stripped away the Firebase CLI as a dependency and makes it easier for you to set up your CI. Now, using App Distribution with fastlane is as easy as running `fastlane add_plugin firebase_app_distribution` to get up and running.
`fastlane add_plugin firebase_app_distribution`
All these improvements made to App Distribution wouldn’t be possible without the active Firebase community. With your help, we’ve been able to add more features to our fastlane plugin and it’s possible to use App Distribution in even more ways. Special shout outs to @gunes_dev, who wrote and maintains our Bitrise Step and @ziebawojtek for all of their contributions and support of the App Distribution GitHub Action.
Your feedback has helped shaped our product and our team loves hearing from developers in the #app-distributions Firebase Community Slack channel - come say hi!
There's a lot more in the works with some exciting features launching soon; you can sign up for our Alpha Program to be eligible to try them out. For developers looking to get started with App Distribution, check out our docs.