Firebase Firestore Rules with Custom Claims - an easy way
Tags: security, firebase, googlecloud, architecture
Published: 2020-08-24 04:34:55

Firebase Firestore rules are a great tool to provide access control and data validation in a simple and expressive format, it has a great [documentation](https://firebase.google.com/docs/firestore/security/get-started) and videos. 


[Custom Claims](https://firebase.google.com/docs/auth/admin/custom-claims) provides the ability to implement various access control strategies, including role-based access control, in Firebase apps. These custom attributes can give users different levels of access (roles), which are enforced in an application's security rules.


Both together are awesome but I found that the implementation could be a little bit confusing if you are not familiar with Firebase Authentication, so in this post we are going to create a strategy where we can take advantage of both of them.

>For this post I will simplify as much as possible the structure of the _Collections_ and _Documents_.

##The Case

We have a _Collection_ of articles with a title and a content.

![Articles Collection](https://dev-to-uploads.s3.amazonaws.com/i/8gb9tp92kqs4m54ev9jj.png)

And we want that:
* **Editors** can _update_ those articles
* **Viewers** can _read_ those articles

So we create a _Collection_ of access (roles).

![Access collection](https://dev-to-uploads.s3.amazonaws.com/i/xlqqflyyj9pq5hr48f1t.png)
As you can see we reuse the Firebase Authentication User UID as a document id.

##Without Custom Claims
If you are not using Custom Claims, your Rules will look something like this:

![Rules without custom claims](https://dev-to-uploads.s3.amazonaws.com/i/03q0meauxh1ex1ycd1ad.png)

There are two functions that read the _Collection_ of access in order to know if the user (UID) can read/update the article.

This works, but there are two reads, one for the request itself and one more to check the access level, so we are duplicating the number of reads and if an API wants to use this logic the code will have to read that collection too.

We can improve that.


##With Custom Claims
####Ok, first at all: What is a claim? 

Basically is the information that [Firebase Authentication](https://firebase.google.com/docs/auth) will return each time you validate a token. 

For example:
```javascript
{
    "aud": "my-project",
    "auth_time": 1234567890,
    "email": "alvardev@example.com",
    "email_verified": true,
    "exp": 1234567980,
    "firebase": {
        "identities": {
            "email": [
                "alvardev@example.com"
            ],
            "google.com": [
                "1234567890123456789012"
            ]
        },
        "sign_in_provider": "google.com"
    },
    "iat": 1234567890,
    "iss": "https://securetoken.google.com/my-project",
    "name": "Alvaro David",
    "picture": "https://urlformypicture.com/image",
    "sub": "123abc123abc123abc123",
    "user_id": "xIR9cPwbHBQb"
}
```

This information can be read by the Firestore Rules.

The strategy is to add some fields to this information and avoid to do a second read on our collections.

>Custom claims are only used to provide access control. They are not designed to store additional data (such as profile and other custom data). Please read the [docs](https://firebase.google.com/docs/auth/admin/custom-claims#best_practices_for_custom_claims).

In this case, we will add the **'level'** field:
```javascript
{"level": "editor"}  // or viewer
```

So our rules will be like:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/s6rp12t6ordhz2enio52.png)

Now it looks cleaner.

##Sounds good... but how to implement that?

In both cases (with and without claims) the start point is the _Collection_ of access, every time we update a document in this _Collection_ we have to set the Custom Claim.

The perfect resource for this task is [Firebase Functions](https://firebase.google.com/docs/functions/firestore-events)! Every time a document is update we can trigger a functions that can set the custom claim based on the content updated.

index.js
```javascript
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.updateAccess = functions.firestore
  .document('access/{accessId}')
  .onUpdate((change, context) => {
    
    const newValue = change.after.data();
    const customClaims = {
      level: newValue.level
    };
		
    // Set custom user claims on this update.
    return admin.auth().setCustomUserClaims(
               context.params.accessId, customClaims)
    .then(() => {
      console.log("Done!")
    })
    .catch(error => {
      console.log(error);
    });

});
```

deploy
```script
firebase deploy --only functions
```

Now our claim looks like:

```javascript
{
    "aud": "my-project",
    "auth_time": 1234567890,
    "email": "alvardev@example.com",
    "email_verified": true,
    "exp": 1234567980,
    "firebase": {
        "identities": {
            "email": [
                "alvardev@example.com"
            ],
            "google.com": [
                "1234567890123456789012"
            ]
        },
        "sign_in_provider": "google.com"
    },
    "iat": 1234567890,
    "iss": "https://securetoken.google.com/my-project",
    "name": "Alvaro David",
    "picture": "https://urlformypicture.com/image",
    "sub": "123abc123abc123abc123",
    "user_id": "xIR9cPwbHBQb",
    "level": "editor"  // There you are!
}
```

That's it! with these few steps we implemented a Custom Claim to enforce our Firestore Rules, those claims are easy to update due to the Firebase Functions triggers.

Hope it helps and my the source be with you :)