3

In Firebase Database; If user_a has data they can access and they want to share this data with user_b, what is the best practice and database structure for securely sharing this data between specific users?

Important: user_a doesn't have any information about user_b account, e.g. uid.


Detailed Example:

1) user_a has a list of clientele.

"users": {
    "user_a": {
        "clients": [
            "clientUid",
            "clientUid2"
        ]
    }
},
"clients": {
    "clientUid": {
        "firstName": "John",
        "lastName": "Doe"
    },
    "clientUid2": {
        "firstName": "Joe",
        "lastName": "Bloggs"
    }
}

2) user_b signs up. user_a now wants to share user_b data in clients with the user_b account that signed up.

Put another way: user_a has a list of clients, one of them creates an account and needs to link to the information user_a has already entered for them.


An important element here is that there is no list of users or accounts that a 'friend request' can be made from for more data. I have experimented with creating a short unique id a user can enter when they sign up to access their data, but am unsure if this is a good way forward and have encountered issues.

This is different to previously asked 'shared data' questions as it focuses on, how is the link between the two users made? Not just the before and after.

halfer
  • 19,824
  • 17
  • 99
  • 186
Josh Kahane
  • 16,765
  • 45
  • 140
  • 253

1 Answers1

5

I know if two ways:

  • Either the users must both know a certain value, known as a shared secret.
  • Or you allow the users to search for each other.

Shared secret

A shared secret can be easily modeled in the database as a location that you can read once you know it. Your short unique id is an example of such a shared secret, where user_a "determines" the secret, and then sends it to user_b out of band.

A simple flow:

  1. user_a clicks a Get secret button in the app
  2. The app determines a secret code, e.g. correct-horse-battery-staple and:

    2a. Writes the secret code to the database in a non-queryable-but-readable location, together with the actual information to share, in your case the UID of user_a.

    2b. Shows the secret code to user_a.

  3. user_a copies the code and sends it to user_b via some other communication mechanism, e.g. text, email, chat.

  4. user_b clicks Enter secret button in the app, and pastes the secret.
  5. The app reads the location, and is able to read the UID of user_a.

The data structure for this could be something like:

"secrets": {
  "correct-horse-battery-staple": "uidOfUserA"
}

Which you secure with these rules:

{
  "rules": {
    "secrets": {
      "$secret": {
        ".read": true
      }
    }
  }
}

So the above rules don't allow anyone to read /secrets, hence they can't get a list of all secrets. But once they know a secret, they can look up the associated data under it.

Allowing search

The alternative is to have a list of users in your database, and allow user_b to search on some property that they know from user_a. A common property might be their email address, or their display name, which you could store in the database like:

users: {
  "uidOfUserA": {
    email: "userA@domain.com",
    displayName: "user A"
  },
  "uidOfUserB": {
    email: "userB@anotherdomain.com",
    displayName: "user B"
  }
}

To allow users to search each other is a bit more involved, as in: it will require server-side code. The reason for this is that being able to search over a dataset requires that you can read that dataset. And in Firebase, if you can read a dataset, you can get all data under it. In other words, to allow search a user you need to be able to read all of /users and allowing this would disclose too much information.

So to implement a search securely, you'll need to have rules like this:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": true,
        ".write": "auth.uid === $uid"
      }
    }
  }
}

In words: anyone can read the profile for a user for whom they know the UID, but each individual user can only modify their own profile.

With this, you can implement a Cloud Function that then takes the email or display name as input, and searches across all /users. Since Cloud Functions access the database with administrative privileges, they are not restricted by the above security rules.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for your prompt and detailed answer Frank. I need to think about how the 'secret' solution could apply to my database and might come back for some clarification on that. I've not used it, but am I right in thinking Firebase Dynamic Links might be an applicable use here? – Josh Kahane Dec 14 '18 at 18:33
  • For what would you use Dynamic Links? – Frank van Puffelen Dec 14 '18 at 19:04
  • I could be completely mistaken, but would it be a valid use them as a means to, for example, have a link in an email that's goes to `user_b` upon clicking takes them into the app and display the 'secret' data. Could a dynamic link carry the secret code? (Realise we are reaching into an entirely new topic here, stop me anytime.) – Josh Kahane Dec 14 '18 at 19:34
  • 1
    That would indeed work. It's step 3 in my recipe above: dynamic links would be the out of band communication mechanism in this case. – Frank van Puffelen Dec 14 '18 at 19:37
  • When creating a secret code, how can I ensure that it will not be a duplicate? Would Firebase Cloud Functions be suitable to generate and send back a custom uid? As a standard uid from say `.push` isn't user friendly for manual entry (when other automated methods aren't available). – Josh Kahane Dec 15 '18 at 17:14
  • That can be done from the client or from Cloud Functions equally. Generate a code, write it using a transaction. Be sure to reject unwanted writes in security rules. – Frank van Puffelen Dec 15 '18 at 17:43