11

I know there are several questions regarding this (e.g. https://stackoverflow.com/a/52808572/3481904), but I still don't have a good solution for my case.

My application has Groups, which are created/removed dynamically, and members (users) can be added/removed at anytime.

Each Group has 0..N private files (Firebase Storage), saved in different paths (all having the prefix groups/{groupId}/...).

In Firestore Security Rules, I use get() & exists() to know if the signed-in-user is part of a group. But I cannot do this in the Firebase Storage Security Rules.

The 2 proposed solution are:

  • User Claims:

but the token needs to be refreshed (signing out/in, or renewing expired token) which is not acceptable for my use case, because users need to have access immediately once invited. Also, a user can be part of many groups, which can potentially grow over 1000 bytes.

  • File Metadata:

but Groups can have N files in different paths, so I will need to loop-list all files of a group, and set the userIds of the group-members in the metadata of each file, allowing access to it. This would be an action triggered by Firestore (a Firebase Function), when a member is added/removed.

I don't like this approach because:

  • needs to loop-list N files and set metadata for each one (not very performant)
  • To add new files, I think I would need to set create to public (as there is no metadata to check against yet), and then a Function would need to be triggered to add the userIds to the metadata
  • there might be some seconds of delay to give files access, which could cause problems in my case if the user opens the group page before that time, having a bad experience

So, my questions are:

  1. Is there a better way?
  2. If I only allow the client to get and create all files when authenticated (disallowing delete and list), would this be enough for security? I think that there might be a chance that malicious hackers can upload anything with an anonymous user, or potentially read all private group files if they know the path...

Thanks!

ernewston
  • 923
  • 6
  • 22

2 Answers2

5

If custom claims don't work for you, there is really no "good" way to implement this. Your only real options are:

  • Make use of Cloud Functions in some way to mirror the relevant data from Firestore into Storage, placing Firestore document data into Storage object metadata to be checked by rules.
  • Route all access to Storage through a backend you control (could also be Cloud Functions) that performs all the relevant security checks. If you use Cloud Functions, this will not work for files whose content is greater than 10MB, as that's the limit for the size of the request and response with Cloud Functions.

Please file a feature request with Firebase support to be allow use of Firestore documents in Storage rules - it's a common request. https://support.google.com/firebase/contact/support

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thanks for the fast response! I think option #1 you mention it's the solution I mentioned as "File Metadata", there I explain the reasons why it's not ideal for my case. Correct me if I'm wrong. Option #2, files could potentially be more than 10mb, but is not very likely. Is there any code examples for this? I guess it's not trivial and I might lose some of the Storage advantages :/ I already submitted a features request. Thanks again. – ernewston Nov 29 '19 at 01:21
  • There are a lot of official samples in GitHub. But you're probably better off with a web search. https://github.com/firebase/firebase-functions – Doug Stevenson Nov 29 '19 at 01:38
  • I just noticed, I’m adding an UUID post-fix to each uploaded file for uniqueness proposes (e.g. `groups/{groupId}/images/{imageId}/imageName-{UUID}.png`). If I set in the security rules that `list` is not allowed, allow `get` and `create` only to auth users (Admin SDK for the rest of operations), and bc the path is almost impossible to brute-guess, wouldn’t it be a form of security through obscurity? If that’s the case, maybe that’s enough for my case, or at least for the time being until Firebase implements a new solution to this. Unless there is something I’m missing…? – ernewston Nov 29 '19 at 04:34
  • It sounds like you have a whole new question to post separately. – Doug Stevenson Nov 29 '19 at 04:47
  • Done: https://stackoverflow.com/q/59110124/3481904. Thanks :) – ernewston Nov 29 '19 at 19:32
1

I had similar use case, here’s another way to go about it without using file metadata.

  1. Create a private bucket

  2. Upload files to this bucket via cloud function

    2a. validate group stuff here then upload to above bucket.

    2b. Generate a signed url for uploaded file

    2c. Put this signed URL in Firestore where only the group members can read it (eg. /groups/id/urls)

  3. In UI get the signed URL from firestore for given image id in a group and render the image.

Because we generate the signed URL and upload file together there will be no delay in using the image. (The upload might take longer but we can show spinner)

Also we generate the URL once so not incurring any B class operations or extra functions running every time we add new members to groups.

If you want to be more secure you could set expiry of signed urls quite short and rotate them periodically.

Kashif Siddiqui
  • 1,476
  • 14
  • 26