Here is my take on multi-tenancy within Firestore and using rules to keep you protected. My example may not work for you but it is to spark ideas.
A good general structure for mutli-tenancy will be
users_collection
'2314234': {
name: 'John Doe',
active_org: {
name: 'Test Org',
id: '13tA03234ze'
}
...more_user_data
} // end of users_collection
user_tokens // name this whatever you'd like and nobody writes to this collection. LOCK IT DOWN READ_ONLY
'2314234': {
org: '13tA03234ze',
role: 'admin',
update_time: last_updated_timestamp
}
orgs_collection
'13tA03234ze' : {
name: 'Test Org',
...more_org_data_if_needed
org_users_subcollection { // this isn't technically nested but best example
'2314234' : {
org_name: 'Test Org', // saved so user has access when they retrieve all orgs they are members of
org_id: '13tA03234ze', //save again so user has access to activate org
name: 'John Doe',
role: 'admin', // this should only be changed here and by approved users
status: 'active' // this could be a boolean whatever or perhaps you have a disabled role...
}
}
}
Now an organization has an easy way to show its list of users and their permissions by calling its sub-collection, and you can gather all of the organizations the user belongs to by doing a collection group query -
info.
But, now we need to address the rules - I generally prefer to keep this data in the Firebase Auth Token and for a few reasons.
- I generally need the active_org_id for storage and firestore rules
- Lowers my queries on the db as I can store the data there.
Don't store too much data in the custom auth token, I generally prefer to add the following
org: '13tA03234ze',
role: 'admin'
To set up the custom claims I use a Firebase Function which listens for changes to the user_tokens collection. Whenever data there changes it will set the new custom claims.
The updates to user_tokens is only done with Firebase Functions - we have a function that listens for users changes and if the user changes their active_org, the function will query the org_users_subcollection and retrieve the data and persist the role & org_id to the user_tokens.
And, we have another Firebase Function that listens to org_users_subcollection, and if data changes (i.e., role), it will query the user_tokens, and if the org_id of the org_users_subcollection matches the org_id of the user_tokens we update the user_tokens with the new role value and we are sync.
And, best of all, you can now also sync your client-side real-time by using a Firestore listener to read user_tokens for the currently authenticated user and if that listener pushes new data (i.e., a role update) you would use the firebase auth client to refresh the id_token by calling user.getIdToken(true) from within your onAuthStateChanged handler for example.
I think that about covers it from our end... hope it helps a bit.