What's Firebase Firestore Rules?
Hello developer pal!, glad to see you here.
Rules, rules and rules, we always hear about rules to follow for interacting with databases, endpoints, programming languages, and well, Firebase Firestore
is not the exception to the...Rule
(dammit once again!).
Anyway, when you work with Firebase you see the features related to store some kind of information have their own Rules
tab, this is the way you can declare for allowing/denying the access to certain resources based on the user who is trying the request.
A bad practice is to keep the resources open for everybody throughout the web, if so, anyone could perform CRUD
operations on your site/app, modify assets, or even remove collections(and I am pretty sure you don't want that, do you?), you can read more information right here.
Show Me The Code
Disclaimer: For this post, a shallow explanation will be given related to Firestore ans Security Rules version 2
, released on May 2019
The 3 main pieces to be focus on are:
- Default versions for test and prod
- Writing rules straight in console vs versioned file
- Allow/Deny access according to auth states and functions
Default versions for test and prod
Whenever you start a new Firebase project, in the section Firestore/Rules
, creating a new db project will present 2 options, you can opt any of those in, let's see the difference:
Mode Production
Under this mode, any access is explicitly denied, this forces the developer to add some logic for explicitly allowing users to access the resources.
The default schema for production mode
looks like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Something to remark is the fact that the rules keep the track on a historical, this means that is possible to activate a previous rule schema, compare a former version against the most recent one, and even delete unused schemas; this also helps to easily find bugs when adding new docs or collections.
Mode Test
Under this mode, any access is explicitly allowed to any user for the next whole month(by default through a timestamp). This will allow the developer to start the work right away, though, the idea is set the schema as soon as possible for allowing only expected users to consume resources.
The default schema for test mode
looks like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.time < timestamp.date(2021, 4, 20);
}
}
}
Just like in production mode, the rules keep the track on a historical, also, a few days before due date, the main email registered will start receiving notifications about the expiring access to the database collections and docs unless a new rule schema is applied.
Writing rules straight in console vs versioned file
Straight in Console
Writing straight to Firebase Console
is an option, it is easy and fast.
One more feature with this approach, is the integration with an sort of built-in linter, it determines some syntax issues before publishing, in fact, it throws an error, and the changes wont be published till the issue is fixed.
Versioned File
A cleaner way to have the rules is through a versioned file, in your firebase.json file, you can add an entry for firestore/rules
(and even indexes
!).
{
"hosting": {
...
},
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
...
},
"emulators": {
...
}
}
Then you can add the firestore.rules
file and keep the versions in git or any other version handler
The flow goes as shown below, if more info required, take a look at the documentation right here.
// Set up Firestore in your project directory, creates a .rules file
firebase init firestore
// Edit the generated .rules file to your desired security rules
// ...
// Deploy your .rules file
firebase deploy --only firestore:rules
Allow/Deny access according to auth states and functions
Either way the writing of rules goes, the critical part is the access to docs and collections. It is possible to create js functions for avoid duplicating conditionals for every element, I wrote a post related to Adding roles to the authentication with Vue(x)+Firebase in case you want to check the use of claims and token additions.
So, for example, you could add a function for determining whether a request comes from an Admin
or a Regular
user profile, according to the resolution(handle by Firebase itself), the access to different resources is granted or not.
Take a look at the example below:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// true if the user is signed in and the claim is admin
function isAdmin() {
return request.auth.uid != null && request.auth.token.admin == true;
}
// true if the user is signed in and the claim is regular
function isRegular() {
return request.auth.uid != null && request.auth.token.regular == true;
}
// Shared collections
match /settings/{doc} {
allow read: if isAdmin() || isRegular();
allow write: if isAdmin();
}
...
...
...
}
}
What happened in the code above?:
- The functions created always ask whether the request incoming is related to a user authenticated, otherwise, the access is invalid and the request is denied
- The function
isAdmin()
, when is invoked by an authenticated user, it looks for a particulartoken
, in this case, the admin token, if presented, the request is validated - The function
isRegular()
,just likeisAdmin()
looks for a particulartoken
, in this case, the regular token, if presented, the request is validated - There is a collection of
settings
, when a request forreading
comes, the fetching is available only for authenticatedusers
with a role ofadmin
orregular
- In the same collection of
settings
, when a request forwriting
comes, the upsert is available only for authenticatedusers
with a role ofadmin
This is useful since even when the APIkey of your app/site is available for third-parties, the requests wont do any operations to your data without an authenticated-and-roled user.
Sometimes read
and write
could be to macro, you can granulate them a bit more:
_read
rule can be broken into get
and list
_write
rule can be broken into create
, update
, and delete
More info about this topic can be found right here
Conclusion
As shown above, Firebase Firestore rules are quite powerful, allowing even write some functions in the declared schema for avoid repeating the code over and over again; maybe you could have a better way to do it, let's discuss in a thread below!
Thanks for reading!
Top comments (0)