javascript - Using firebase query-based rules to secure data (with orderByChild and an un-indexable key)


Keywords:javascript 


Question: 

Update / Answer Feb 2018

It seems Query-based rules are currently unable to afford this level of security / elegance to the realtimeDB structure. Something like this would require nested sorting, variable 'key' indexes (as with the current "indexOn": ".value", or some other magic yet to be dreamed up by the firebase team.

In the meantime I can definitely recommend reading up on query-based rules:


Original Q: Given a list of firebase realtime-db nodes, each with a named list of groups, and a group I would like to lookup - I am looking to restrict access via query-based rules so that only nodes with the given group are returned. Everything else should be considered private data.

E.g DB:

[lookup]: {
  id1: {
    groups: {
      randomgroup1: true
    },
  },
  id2: {
    groups: {
      randomgroup1: true,
      randomgroup2: true
    },
  }
}

Realtime DB Rules:

"lookup": {
  ".read": "query.orderByChild != null && query.equalTo != null",
  "$uid": {
    ".indexOn": "groups"
  }
}

And valid request/query:

const $group = 'randomgroup2';
firebase.ref('lookup').orderByChild(`groups/$group`).equalTo(true).once('value')

The above correctly returns id2: { groups: { randomgroup1: true, randomgroup2: true }} HOWEVER the entire [lookup] node is sent to the client to be filtered due to the lack of an index on the group names, i.e. the data is not secure.

How best to do this so to retain security? I am open to changing the structure of the nodes, or updating the security rules.

Note: this is entirely possible with the tried and tested many-to-many relationship setup (i.e. firebase brute-force duplicate databasing 101), it is similarly possible (and secure) with the technique above where only 1 group is required per record.


1 Answer: 

Your current data structure is well suited to look up the groups for a given id.

But to look up the ids for a given group you will need to execute a query, as you're trying to do.

For the query to be executed on the server, you need to have an index defined on the field you order on, so groups/$group. That means you will need to define an index on each groups/randomgroup1, groups/randomgroup2, etc. This is not feasible.

That's why you'll want to add another node to your data structure that contains precisely the information you want to look up: the ids for each randomgroup:

[lookup2]: {
  randomgroup1: {
    id1: true
  },
  randomgroup2: {
    id1: true,
    id2: true
  }

Now with this structure you can also directly look up the ids for a given group.

For more on such bidirectional lookups, see: