How to implement security rules for sharing access to documents

111 views
Skip to first unread message

Henry Wu

unread,
Jul 11, 2019, 9:37:00 AM7/11/19
to Firebase Google Group
Hello,

I'm working on a React app that uses Firebase as the backend (we use Firestore as the DB), and am seeking some guidance on how best to implement share functionality for our app.

Context

Our app supports creating spreadsheets (for example, something like Google Sheets) that by default are visible only to the creator.
The Firestore security rules on the collection backing the spreadsheets allows read / write only by the creator of the resource.

Existing security rules for the Spreadsheet collection:
read: current auth user is logged in and matches the one on the resource being requested
write: current auth user is logged in and matches the one on the resource being written to

This works nicely for the private case, where we want to limit viewability to only the creator of the spreadsheet. But we're having a few problems implementing share functionality, detailed below.


Problems

Problem 1 - Supporting Viewers
One of the features we want to support is to provide "public" access to a spreadsheet, in a very similar vein to what Google Docs/Sheets supports. Given a spreadsheet, you can generate a link that allows anyone who has that URL to view that spreadsheet (but not edit it). We'd like this to support users who are not logged in or do not have an account set up with Firebase.

(Hacky) solution
The way we've gotten around this right now is by utilizing the idea of Firebase anonymous users (https://firebase.google.com/docs/auth/web/anonymous-auth).
- Create a subcollection within the Spreadsheet collection named "viewers", with the resource id of each viewer document being a user_id (the "viewer").
- Upon trying to access the "public" version of the URL, we add the current user to the viewers subcollection of the spreadsheet being viewed. If the user is not logged in, we create and log-in anonymously using Firebase's anonymous auth, and then try again (so this time they should get added as a viewer properly).

Security rules:
read: current auth user is logged in and matches the one on the resource being requested, OR the current auth user is found in the subcollection "viewers" of the resource
write: current auth user is logged in and matches the one on the resource being written to

This works, but seems a little bit hacky. It also pollutes our Users database with garbage anonymous users. We're trying to see if there's a better way to do this.


Problem 2 - Supporting Editors
Another feature we want, similar to "problem 1" above, is the idea to also support "editors" or "collaborators" on a particular spreadsheet (similar to Google Docs/Sheets collaborative editing). As an owner of the spreadsheet, you can generate a link, whereby any logged in (not anonymous) user who has the link is allowed to edit the document.

No solution?
For this problem, we don't have a good solution here. Ideally, we'd want our security rules to look something like this:
read: current user is logged in OR found in "viewers" OR is an approved "editor"
write: current user is logged in OR is an approved "editor"

However, we're stuck on how to add people as editors, based on only having access to the special link. 

Scenario:
  1. Creator generates a link to the page and sends it to her friends that she wants to collaborate with. 
  2. Friend accesses the URL
  3. Friend gets added into the "editors" list of the document so that they have write access to it going forward. 
We're not sure how to set up the rules properly though to allow this though, since we don't want to open up the write access to the collection to allow anyone to add themselves as an editor.

We've temporarily gotten around this by just not having the "get collaborative link" and instead explicitly require the creator to set the emails/user_ids of the collaborators, which we store on the spreadsheet document itself and check against that. But that's a subpar experience so we're trying to see if we can get the collaborative link working.


We would appreciate any feedback or advice regarding either of the above problems! Let me know if I can answer any questions or provide more details.

Michael Bleigh

unread,
Jul 11, 2019, 1:05:01 PM7/11/19
to Firebase Google Group
Hi Henry, some thoughts / questions:

Problem 1:

Do your public access links need to be able to be revoked and reissued as a different link? If not, you could simply generate a long, unguessable string to use as the document id and simply have a "public: true|false" field in the document. You could then write your rules like:

match /documents/{id} {
  allow read: if resource.data.public;
}

Thus the only thing the viewer needs to be able to access the document is to know the long unguessable id -- they don't have to be authenticated. There are other more complicated schemes you could implement if you need revoke/reissue.

Problem 2:

The simplest way to do this would probably be with a callable function. You could generate a collaboration_tokens collection that maps to documents, then make a callable function called e.g. joinAsCollaborator. When a user visits the collaboration link, your app could call joinAsCollaborator(collaborationToken). The Cloud Function would then look up the collaboration token, dereference it to the associated document, and add the user to the document (callable functions will contain verified contextual information about the user, so you can trust the data there).

I can imagine ways to do it without a function, but they'd be a little more hacky and overly clever.

Hope that helps!

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/b1aabe99-8a1d-49e6-8a04-1d32b8bd9ac4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Henry Wu

unread,
Jul 14, 2019, 10:47:33 AM7/14/19
to Firebase Google Group
Hey Michael,

Thank you for the response!

A couple of answers / followups to some of your suggestions:

Problem 1:

We don't necessarily need the ability to revoke / reissue the public access link as a different link. However, for your suggestion on adding a field "public" to the resource schema itself, if we did that, then that means anyone (potential attacker) could then query our collection for all resources marked as "public", right? That might conflict a little with the user's expectation when generating the link, which is that only people that he or she explicitly shared the link with would be able to view it. Let me know if I can clarify more here.


Problem 2:

We thought about using callable functions as well, but in our experience sometimes the called function can take some time to return (several seconds), perhaps because of a cold start to load the function? And depending on the latency there it might be a subpar experience. However from your comment that does seem like a good approach to try, we can give it a try and add some measurement to see how slow or fast it is in practice?


Thanks again for your help!

Henry


On Thursday, July 11, 2019 at 10:05:01 AM UTC-7, Michael Bleigh wrote:
Hi Henry, some thoughts / questions:

Problem 1:

Do your public access links need to be able to be revoked and reissued as a different link? If not, you could simply generate a long, unguessable string to use as the document id and simply have a "public: true|false" field in the document. You could then write your rules like:

match /documents/{id} {
  allow read: if resource.data.public;
}

Thus the only thing the viewer needs to be able to access the document is to know the long unguessable id -- they don't have to be authenticated. There are other more complicated schemes you could implement if you need revoke/reissue.

Problem 2:

The simplest way to do this would probably be with a callable function. You could generate a collaboration_tokens collection that maps to documents, then make a callable function called e.g. joinAsCollaborator. When a user visits the collaboration link, your app could call joinAsCollaborator(collaborationToken). The Cloud Function would then look up the collaboration token, dereference it to the associated document, and add the user to the document (callable functions will contain verified contextual information about the user, so you can trust the data there).

I can imagine ways to do it without a function, but they'd be a little more hacky and overly clever.

Hope that helps!

To unsubscribe from this group and stop receiving emails from it, send an email to fireba...@googlegroups.com.

Michael Bleigh

unread,
Jul 14, 2019, 12:38:16 PM7/14/19
to Firebase Google Group
You can control querying separately from getting in Firestore rules ("read" decomposes to "get" and "list"), which is what I'd recommend for the "public" field problem. For instance, to only allow listing of one's own documents you could do:

allow list: if request.auth.uid == resource.data.owner

Or if you have an array of uids such as "viewers":

allow list: if request.auth.uid in resource.data.viewers

To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.

To post to this group, send email to fireba...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages