14

As for security rules of Firebase Realtime Database, both public and private data can exist in the same tree using such as the following rule.

However, when using Firestore, it doesn't seem to enable us to do the same because the chuck of data we can retrieve is only under collection or document. When public and private data is defined in the same document and getting data w/ collection/document, we'd get error of insufficient permissions as for private data if we are not the owner.

When using RTDB, we can get data of 'users/{userId}/publicInfo' because we don't have any idea of collection/document.

Are there any way to do this of RTDB with Firestore? Otherwise, we should have public/private collection separately?

// rule of Firebase Realtime Database
"users": {
   "$user_id": {
       ".read": "auth.uid === $user_id",
       ".write": "auth.uid === $user_id",

       "private": {
          ".read": "auth.uid === $user_id"   // --- private data
       }

       "public": {
          ".read": "auth !== null";           // --- public data 
       }
   }
}

// Firestore
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {

      match /{private=**} {
        allow read, write: if request.auth == userId;
      }

      match /{public=**} {
        allow read, write: if request.auth != null;
      }
    }
  }
}
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
miz-k
  • 965
  • 1
  • 7
  • 9

2 Answers2

23

So you can't have separate security rules for separate parts of a document. You can either read the entire document, or you can't.

That said, if you wanted to give your userID document a "public" and "private" subcollection that contained documents that were public and private, that's something you can totally do, just not in the way you've currently set up your security rules.

The match /{private=**} bit as you've written it doesn't mean, "Match any subcollection that's called 'private'". It means, "Match any subcollection, no matter what, and then assign it to a variable called private". The "Recursive matching with wildcards" section of the docs covers this in more detail.

Also, you need to reference request.auth.uid to get the user's ID.

So, you probably want something more like this:

// Firestore
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      // You'll probably want to add security rules around the user document 
      // itself. For now, though, let's look at our subcollections:

      match /private/{anything=**} {
        // Only the user can read documents in their private collection
        allow read, write: if request.auth.uid == userId;
      }

      match /public/{anything=**} {
        // Anybody can read documents here, as long as they're signed in
        allow read, write: if request.auth != null;
      }
    }
  }
}
Todd Kerpelman
  • 16,875
  • 4
  • 42
  • 40
  • Thanks a lot. Understood Firestore enables us not to put document these kinds (public/private) of data in the same doc. As you said, possible solution is to have sub collection but this enforces us to have a key of document, which isn't needed this case; e.g. /users/{uidxx}/private/{uidyyy}/firstName, etc, even though the same uid is allowed. It should be noted when retrieving data from collection, data of sub collection doesn't included, isn't it. – miz-k Oct 05 '17 at 22:57
  • 1
    Keep in mind you can also specify the key itself when creating a new document. So you could have /users/{uidxx}/private_data/private/firstName and that would work just fine. Also, probably having a separate collection for your public data might not be necessary -- you could keep that in the parent doc. – Todd Kerpelman Oct 06 '17 at 18:54
  • 4
    Is this considered best practice over maintaining two separate users collections for public and private data (then syncing via cloud functions)? – Théo Champion Jun 10 '19 at 06:58
  • You will be limited when filtering documents if you separate fields into sub collections. You can not directly fetch all parents 'where child.field == true' – Frank Jan 14 '20 at 09:42
  • What is wrong with using the same root but have each document have a `visibility` property with either: `public`/`private` as values. And write your security rules like that? Like the example in the firestore docs. This also makes it extensible with `shared` or more props. And you wouldn't have to duplicate indexes... – TrySpace Jan 20 '22 at 19:57
  • @Frank do you have an alternative approach? Im struggling to find the best way to model data that has public and private readable fields – Lucas Rahn Jul 06 '23 at 20:09
0

You can add a visibility property with either: public/private as values to each document, and make sure you have a userid string or array (for multiple user access)

Then you can write your security rules to check the properties visibility and userid.

Check the example in the firestore docs.

Extras

  • This also makes it extensible with shared or more props.
  • You wouldn't have to duplicate indexes
  • You don't have to move a document when changing visibility
TrySpace
  • 2,233
  • 8
  • 35
  • 62
  • If i understand this correctly, you could only limit the visibility of each individual document. OP asks to limit visibility of specific attributes within a doc. – Lucas Rahn Jul 06 '23 at 19:50