1

I have a Firebase database that I want to only allow users who have access to that application to be able to read from and write to.

My data structure is like so:

{
  "applications": {
    "id_1": {
      "feature": {
        "a": true,
        "b": false
      },
      "users": {
        "user_id_1": true,
        "user_id_2": true
      }
    }
  }
}

As you can see, each application can have many users who have read/write access.

I only want users in the users object to be able to retrieve that application.

I have rules like so:

{
  "rules": {
    "applications": {
      ".read": "auth != null",
      ".write": "auth != null",
      "$appId": {
        ".write": "data.child('users').child(auth.uid).val() === true",
        ".read": "data.child('users').child(auth.uid).val() === true"
      }
    }
  }
}

".read": "auth != null", allows any user who is logged in to be able to retrieve all applications. I only want users user_id_1 or user_id_2 to be able to read that application.

In pseudo code, I would do something like this:

{
  "rules": {
    "applications": {
      ".read": "only users in `root.applications.$appId.users` can read", // I need to replace `$appId` some how
      ".write": "auth != null",
      "$appId": {
        ".write": "data.child('users').child(auth.uid).val() === true",
        ".read": "data.child('users').child(auth.uid).val() === true"
      }
    }
  }
}

How can I restrict it so when user user_id_1 fetches their applications, they only see apps they have access to?

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Stretch0
  • 8,362
  • 13
  • 71
  • 133
  • 1
    The Firebase Realtime Database and Cloud Firestore are two separate databases. Please only mark your question with the relevant tag, not with both. – Frank van Puffelen Jan 14 '22 at 15:29

1 Answers1

2

You're hitting a few common problems here, so let's go through them one by one.

1. Security rules can't filter data

You're trying to control in your rule on /applications what application will be returned when a user tries to read that node. Unfortunately that is not possible, because security rules grant all-or-nothing access.

So either the user has access to /applications (and all data under it), or they don't have access to it. You can't set a rule on /applications to grant them access to some child nodes.

In the documentation, this is referred to as rules are not filters and the fact that permission cascades.

2. Avoid nesting data

You're trying to grant access to /applications, but then store two types of data under there for each application. In cases like that, it is usually better to store each type of data as its own top-level list.

So in your case, that'd be:

{
  "application_features": {
    "id_1": {
      "a": true,
      "b": false
    },
  },
  "application_users": {
    "id_1": {
      "user_id_1": true,
      "user_id_2": true
    }
  }
}

This allows you to grant separate access permissions for the application users and its features. While it means you'll have to read from both branches to get all information of each user, the performance difference there is negligible as Firebase pipelines those requests over a single socket

For controlling access and the most scalable data structure, Firebase recommends that you avoid nesting data and flatten your data structure.

3. Model the data in your database to reflect the screens of your app

Since granting anyone access on /applications gives them access to all data under that, you'll likely need another place to store the list of applications for each user.

I usually make this list explicit in my databases, as another top-level list:

{
  ...
  "user_applications": {
    "user_id_1": {
      "id_1": true
    },
    "user_id_2": {
      "id_1": true
    }
  }
}

So now when you want to show the list of applications for the current user, you load the IDs from /user_applications/$uid and then look up the additional information for each app with extra calls (which in turn can be pipelined again).

This one is not in the documentation, but a common pattern with NoSQL databases. I recommend checking out my answers to Many to Many relationship in Firebase and Firebase query if child of child contains a value.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for the detailed response Frank. Very much appreciated. Those docs make sense but one thing I don't understand is what is preventing any user from updating `user_applications` to give themselves access to any application? i.e. user `user_id_1` can add an application that belongs to `user_id_2` to `user_application.user_id_id` object. By having `users` nested inside the application, only users who already had read access could add users but doesn't seem like this would be possible if I flatten the structure. Maybe it can be handled by using roles though? – Stretch0 Jan 14 '22 at 16:06
  • 1
    Admin functionality is always app specific, but the same is possible with either data model. For example, you could check if the UID of the writer exists in `/application_users/$appid/$uid` already. You could even store their role there (`"id_1": "admin"`, `"id_2": "user"`) and check for that specific value instead of just presence. – Frank van Puffelen Jan 14 '22 at 19:22