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 and videos.
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.
And we want that:
- Editors can update those articles
- Viewers can read those articles
So we create a Collection of access (roles).
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:
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 will return each time you validate a token.
For example:
{
"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.
In this case, we will add the 'level' field:
{"level": "editor"} // or viewer
So our rules will be like:
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! Every time a document is update we can trigger a functions that can set the custom claim based on the content updated.
index.js
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
firebase deploy --only functions
Now our claim looks like:
{
"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 :)
Top comments (4)
Hi, is the custom-claims still usable in Firestore Rules ?
It worked for me few months back, but same rule is not working now.
Getting error - Missing or insufficient permissions
Rule used:
I have verified the user has the custom token claim. Printing tokens in console on UI, onAuthStateChanged shows the new token
JS code to print user custom claims in console log:
In firebase documentation also, I see only Database Rules and Storage Rules are mentioned for Custom-Claims. I do not see reference of Firestore Rules - firebase.google.com/docs/auth/admi...
Oh, got it sorted just now.
Issue: Custom claim (level) was String "4"
Fix: Changed it to Number 4
Hi Pradeep-here! Great to know that everything worked :)
Thank you Alvaro, this post was very useful in optimizing my application to use fewer get() calls!