Permission-based access in Google Firestore

I have a Firestore database in my project. I store user data as entries in my database and I want to create my own user roles and permissions and using them in database rules for access control. For this reason, I went with Firestore and not Realtime Database — it allows for more flexible database rules.

Inspired by Fireship's role-based database access.

Let's start with talking about about pros and cons of this approach

Pros 🟢

  • Define custom permissions.
  • Enclose the permissions into roles.
  • Allow database operations only to users with certain permissions.
  • Easy to grasp and implement.

Cons 🔴

  • Cannot do proper role hierarchy — different roles are not related to each other in any way.

Permission-based vs. Role-based

Another important distinction is, that this approach is Permission based. Although we will introduce few roles later, these are just containers for sets of permissions. You can check out comparison of these approaches here.

The main difference is, that we assign roles to users, but allow actions according to permissions! This gives us best of both worlds — central control of roles (adjust all admins at once) and fine-grain control of locations access.

Creating users

For creating users I'm using Firebase authentication and Google sign in. For every new user that signs in into my web app, I create a small account — just a database entry, really.

  1. Create users collection in your Firestore database
  2. Save each user that logs in in a document named with his UID. Sign in with Google gives you the UID for free, but you can probably generate it too.
  3. Give every newly created user a default role — mine is viewer. It is advisable that the default role has very low privileges.

In your Firestore, user document can look like this

/users/foo :email: "foo@email.com"
role: "viewer"
name: "John Doe"

Each attribute is a separate string field. John's UID here is foo and he has a viewer role.

Creating roles and permissions

For roles, we will create new roles collection with roles document (feel free to pick a better name). Inside, we will create an array of strings field for each role. Name of the role is the name of the field at the same time. And inside the array, there are permissions for each user role.

Permissions and roles document
User permission encapsulated in roles

Database rules!

Finally, we implement database access rules, that take these permissions into consideration.

We will create couple of helper functions:

Rules helper functions
  1. Simple check if the incoming request is authorized
  2. Fetch user role from database with request's ID. This ID has to correspond to user UID — name of his document. Notice how we are getting a snapshot from database and then calling .data.role on it.
  3. Get the array of permissions with user.role key.
  4. userCan function ties all of these together. It's argument is a single permission to allow actions on part of the database.

Usage

Together with rules' granular operations we can have a really fine control over user permissions.

match /users/{userId=*} {
allow read: if isSignedIn() && request.auth.uid == userId;
allow create: if isSignedIn() && request.auth.uid == userId;
allow update: if isSignedIn() && userCan("manageUsers");
allow delete: if false;
}

With this setup a user can create and read his own account details. Only user that has manageUsers permission (currently only admin) can change user details (to prevent users from changing their roles).

Additionally, make the rules publicly known and immutable:

match /roles/roles {
allow read: if true
allow write: if false
}

And there you have it! This is by no means perfect solution for every use case under the sun. But it should give you a hint about Firestore rules' inner workings and what can one do with them. If you iterate on my idea and make it better, please let me know, I would love to hear that :)

I hope this post inspired you! Take my notes, run with them and create something great! ☀️

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Vojta Struhár

Vojta Struhár

I like automating things. | Working as a Web developer. | Wannabe GameDev.