DEV Community

Maciej Modzelewski
Maciej Modzelewski

Posted on • Originally published at climbingdev.com

First take on Cloud Firestore Security Rules

Hi!

I'm currently working on a small POC project and I thought Firebase will be a good candidate for quick prototyping. In the project I am using Cloud Firestore and Authentication.

In this post I'd like to show you what I've learned about the Firestore Rules. Hopefully you'll find it useful. Let's dig in.

The first version of the Firestore schema looks like this:

{
  "clubs": {
    "<clubId>": {
      "name": "This is a public club",
      "ownerId": "<ownerId>",
      "visibility": "public",
      "members": {
        "<userId>": {
          "name": "My fancy nickname"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

At the top level there is a collection of clubs. Each club has a name, owner and it can be either public or private.

Of course the club must have members, which is a nested Firestore collection. The keys in this collection are users' ids from the Firebase Authentication module. A member for now has only a name.

For the above collections schema I have created the following rules:

rules_version = '2';
service cloud.firestore {
 match /clubs/{club} {
        allow create: if request.resource.data.ownerId == request.auth.uid
      allow delete, update: if request.auth != null && request.auth.uid == resource.data.ownerId
      allow read: if request.auth != null && (resource.data.visibility == 'public' || isClubMember(club, request.auth.uid))
      match /members/{member} {
        allow read, write: if isClubOwner(club, request.auth.uid)
      }
    }    

    function isClubMember(clubId, userId) {
      return exists(/databases/$(database)/documents/clubs/$(clubId)/members/$(userId));
    }
    function isClubOwner(clubId, userId) {
        let club = get(/databases/$(database)/documents/clubs/$(clubId));
      return club != null && club.data.ownerId == userId;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's look at them one by one.

 match /clubs/{club} {
Enter fullscreen mode Exit fullscreen mode

All rules within this block will refer to some club with id available under the club variable.

allow create: if request.resource.data.ownerId == request.auth.uid
Enter fullscreen mode Exit fullscreen mode

Anyone can create a club as long as they set themselves as the owner of the club.

allow delete, update: if request.auth != null && request.auth.uid == resource.data.ownerId
Enter fullscreen mode Exit fullscreen mode

Only the owner can delete or update the club.

allow read: if request.auth != null && (resource.data.visibility == 'public' || isClubMember(club, request.auth.uid))
Enter fullscreen mode Exit fullscreen mode

Any authenticated user can see public clubs. Non public clubs can be seen by their members.

match /members/{member} {
    allow read, write: if isClubOwner(club, request.auth.uid)
}
Enter fullscreen mode Exit fullscreen mode

Only the owner can see or modify members of the club.

In the last two rules I have used helper functions which are defined as follows:

function isClubMember(clubId, userId) {
  return exists(/databases/$(database)/documents/clubs/$(clubId)/members/$(userId));
}
Enter fullscreen mode Exit fullscreen mode

This function queries the members collection for specified club to see if the user belongs to it.
Important note: this function will count towards the number of DB invocations and will influence billing.

function isClubOwner(clubId, userId) {
  let club = get(/databases/$(database)/documents/clubs/$(clubId));
  return club != null && club.data.ownerId == userId;
}
Enter fullscreen mode Exit fullscreen mode

This function uses get instead of exists and checks the property of the club to see if the specified user is an owner.

This is what I've come up with on the first encounter with Firestore rules. It's not perfect but it's a good start.

At the moment I'm not sure what is the best place to keep the ownerId. With the current set up every user that can see the club can also see the id of the owner and that's far from perfect.

If you have any comments or suggestions on how this structure could be improved please let me know in the comments.

Happy coding! 🙂

Additional resources:
https://firebase.google.com/docs/rules
https://firebase.google.com/docs/firestore/security/rules-query

Top comments (1)

Collapse
 
thanhlm profile image
Thanh Minh

Great post! But working with Firestore on the web is something terrible for me!