40

Can we use Firestore data to grant or restrict access to files hosted on Firebase Cloud Storage?

Exemple of what I would like to use as Firebase Security Rule

allow write: if get(/databases/mydbname/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"];
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Remi
  • 693
  • 8
  • 17

3 Answers3

40

Update (Oct 2022): it is now possible to access Cloud Firestore from within your Cloud Storage security rules with the new firestore.get() and firestore.exists() functions. See the blog post Announcing cross-service Security Rules and the documentation on enhancing Cloud Storage security rules with Cloud Firestore.

Previous answer below for reference:

There is currently no way to access different Firebase products from within the security rules of another product. See: is there a way to authenticate user role in firebase storage rules?

But it seems like you are trying to check group-membership of the user. Instead of looking that group-membership up in the database, I recommend that you model it as a so-called custom claim. Instead of (or in addition to) writing the membership to the database, you'll set the claim "user {uid} is a member of group {guild1}" into the user profile using the Firebase Admin SDK:

admin.auth().setCustomUserClaims(uid, {guild1: true}).then(() => {
  // The new custom claims will propagate to the user's ID token the
  // next time a new one is issued.
});

With that done, you can check the guild membership in the security rules:

allow read: if request.auth.token.guild1 == true;

(I'm not sure if/how you can model the guild membership as a map, I'll update this answer if that turns out to be the case.)

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    make sense. will try to populate custom claims based on database using cloud firestore triggers and will see if that make it – Remi Oct 21 '17 at 15:42
  • 6
    @frank-van-puffelen Is this still the case as of 2021 – Andrew Jan 28 '21 at 20:03
  • 2
    Both the fact that you can't access other products from within security rules and the workaround are still valid yes. – Frank van Puffelen Jan 28 '21 at 20:05
  • You could also do a [cloud storage trigger](https://firebase.google.com/docs/functions/gcp-storage-events) which ultimately deletes the upload if it is not validated through firebase. – Jonathan Sep 18 '21 at 22:33
  • Yup, that's always an option @Jonathan. If you can elaborate a bit more on how to do that, it might make a good alternate answer. – Frank van Puffelen Sep 18 '21 at 22:48
8

Firebase recently announced cross-service security rules that let's you access Firestore data in Firebase storage's security rules. You just need to use firestore. prefix before get() and exist() functions as shown below:

allow write: if firestore.get(/databases/(default)/documents/col/docId/).data.field == "value";

Firebase current supports only 1 database instance per project so the name must be (default) in path. It's not a wildcard as in Firestore rules so not $(database)

Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
2

Update: As of 2022-09-28, Firebase introduced cross-service Security Rules, so the answer below is outdated. See @Dharmaraj's answer below for an example.

You can retroactively validate and delete the file after it's been uploaded using a cloud function trigger.

Warning: this technique is not bullet proof, as the invalid file will be stored in Cloud Storage temporarily or potentially forever if the Cloud Function trigger fails. My preference is to prevent the upload in the first place, but if the logic to determine permission resides in Firestore and can't be stuffed in custom claims, then this is currently the only way if you're uploading files using Firebase's Client SDKs. If you're building a mission critical system, you should upload the file to a Cloud Function and let the Cloud Function store the file in Cloud Storage instead.

When uploading a file, add some metadata indicating who's doing the upload:

      const storageRef = ref(
        storage,
        `files/${fileName}`,
      );
      const uploadTask = uploadBytesResumable(storageRef, file, {
        customMetadata: {
          uploaderId: userId,
        },
      });

Set storage rule to ensure that the user identity metadata can be trusted:

    match /files/{fileName} {
      allow create: if request.auth != null &&
        request.resource.metadata.uploaderId == request.auth.uid
    }

Create a cloud function trigger that retroactively validates and deletes:

export const onFinalize = functions
  .storage.object()
  .onFinalize(async object => {

    // We can trust object.metadata.uploaderId, so check Firestore if user is able to upload file
    if (!(await canUploadFile(object.metadata.uploaderId, object.name))) {
      await storage.bucket(object.bucket).file(object.name).delete();
      throw new Error(
        `Permission error: ${object.metadata.uploaderId} not allowed to upload ${object.name}`,
      );
    }

    // Continue
  });
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275