Introducing Query-based Security Rules

Securing your Firebase Realtime Database just got easier with our newest feature: query-based rules. Query-based rules allow you to limit access to a subset of data. Need to restrict a query to return a maximum of 10 records? Want to ensure users are only retrieving the first 20 records instead of the last 20? Want to let a user query for only their documents? Not a problem. Query-based rules has you covered. Query-based rules can even help you simplify your data structure. Read on to learn how!

The new query variable

Security rules come with a set of variables that help you protect your data. For instance, the auth variable tells you if a user is authenticated and who they are, and the now allows you to check against the current server time.

Now, with the query variable, you can restrict read access based on properties of the query being issued.

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 100"
}

In the example above a client can read the messages location only if they issue an orderByKey() query that limits the dataset to 100 or less. If the client asks for more than 100 messages the read will fail.

The query variable contains additional properties for every type of query combination: orderByKey, orderByChild, orderByValue, orderByPriority, startAt, endAt, equalTo, limitToFirst, and limitToLast. Using a combination of these properties you can restrict your read access to whatever criteria you need. Get the full reference and see more examples in our docs.

Simplifying data structures

Another benefit of query-based rules is that they make it easier to manage a shallow data structure.

In the past you might index your items location by a user’s id.

{
   "items": {
       "user_one": { 
          "item_one": {
             "text": "Query-based, rules!"
          }
       }
    }
}

This structure made it easy to query and restrict item reads on a per-user basis.

{
  "rules": {
    "items": {
      "$uid": {
        ".read": "auth.uid == $uid"
      }
    }
  }
}

This is great because your user’s items are secured, but it requires you to index off of the user’s id, potentially replicating data or complicating your client code.

With query-based rules, you can now get the same security without the nesting!

{
  "rules": {
     "items": {
        ".read": "auth.uid != null &&
                  query.orderByChild == 'uid' &&
                  query.equalTo == auth.uid"
     }
  }
}

The above rule will restrict any read on a per-user basis without indexing by the "uid" key, which means you can write a simple query to retrieve a user’s items without sacrificing security.

Now the data structure is reduced by one level:

{
   "items": {
       "item_one": {
          "text": "Query-based, rules!",
          "uid": "user_one"
       }
    }
}
db.ref("items").orderByChild("uid")
               .equalTo(auth.currentUser.uid)
               .on("value", cb)  

The above query will retrieve all the items belonging to the currently authenticated user. If a user tries to access items that don’t belong to them, the read will fail.

When should I upgrade?

Query expressions are new feature that can be used in parallel with your existing rules. If you find that query expressions can improve your security or simplify your structures you can upgrade to them incrementally.

Give it a try!

Query-based rules are available for use today. Go to your Firebase console and use the emulator to give them a whirl. Check out our official documentation for more information. If you run into any problems make sure to check out our support channels or hit up the Firebase Slack community.