Firestore, Firebase Authentication, and Firestore Rules with multiple business entities - firebase

I am trying to see if Firestore is the right tool for my need.
I am designing a multi-tenanted system using Firestore for scalability and security. Can this be done correctly using just Firestore, Firebase authentication, and Firebase rules to handle all CRUD operations? The idea behind the entities is:
System > Account > Business > Customer
Roles at each entity level would be:
Administrator, Manager, Support, Report, Unauthenticated (the customer)
Accounts have businesses and businesses have customers. Customers would not need to authenticate but read a specific document (designated by the business) and then create their own specific new document. They could create multiple documents but there would be time-based volume thresholds (no DOS!).
I believe Firestore Rules would work great if it didn’t have the Account as a superset that has multiple of the Businesses.
Is it possible to create a system like this with just Firestore, authentication, and rules alone? I want to keep things simple but also not pound a nail using a pair of pliers.
Thanks in advance for the insight!

With a set of well defined Custom Claims you can indeed use Firestore for such a multi-tenant system.
In your security rules, you would combined the different claims, depending on the access role.
In particular you should:
Check the user has the correct claims e.g. auth.token.businessId == "xyz", auth.token.role == "Support"
Check that the user is writing/reading a doc that corresponds to his entity, e.g. allow create: if request.auth.uid == userId && request.resource.data.businessId == auth.token.businessId
Don't forget that Security Rules are not filters, so in each query executed by a user, you need to add the different ids from his/her custom claims (businessId, accountId, etc...). Either by mimicking your entities tree with (sub)collections, or by using some where() clauses in the queries.
Note that since Customers would not need to authenticate, they could potentially read and create documents from/for others businesses, if they have the ID of these businesses (i.e. the DocumentReferences).
You will probably have to manage a set of claims for each user (role, account, business, ... ), which can become complex, so you might be interested by the following article which explains how to create an Admin module for managing Firebase users access and roles.

Related

How public is "if request.auth.uid != null" as a security rule in Firestore?

I'm using Firestore to store my data. This includes user profile details and their current location in documents, which are stored in a collection with the below security rules:
match /profile/{w9o3948s} {
allow read, write: if request.auth.uid != null;
}
Is there any way for people to "browse" the list of documents in the collection and look through user's locations? Or can that only be done by code within in my app?
The document ID is randomly generated, so even if someone hypothetically knows a current document ID - how would they query the document?
Is there any way for people to "browse" the list of documents in the
collection and look through user's locations?
Yes, with your security rules it is totally possible.
As soon as (1) someone has the apiKey of your Firebase Project and (2) the email/password sign-in method is enabled, this person can use the Firebase Auth REST API and sign-up to your project (i.e. create a new account).
Getting the apiKey is not very difficult if you deploy an app linked to your Firebase project (Android, iOS, Web...).
One standard way to give access to only a set of users (for example, the employees of your company, or some paying subscribers to your app) is to use Custom Claims. You will find in the documentation the guidelines for setting access control with Claims.
You may be interested by this article which presents how to build, with a Callable Cloud Function, a module for allowing end-users with a specific Admin role creating other users and how to restrict access to users with one or more specific Custom Claim(s). (disclaimer, I'm the author).
Or can that (i.e. browse the list of documents in the collection) only
be done by code within in my app?
Anybody who can reverse engineer your app can find the name of your Firestore collections and, with an account created as explained above, can access the documents in those collections.
The document ID is randomly generated, so even if someone
hypothetically knows a current document ID - how would they query the
document?
As you will read in the section of the Security Rules documentation dedicated to Granular Operations, using read allows users to get one document AND to list all documents of a Collection (of a Query). So if you want to restrict the read access rights of a user to only his/her profile, you will need to have two different rules for get and list.
So, in conclusion:
Use Custom Claims: they can only be set from a "privileged server environment" through the Firebase Admin SDK. "Privileged server environment” meaning a server that you fully control or a Cloud Function in your Firebase Project.
Fine tune your Security Rules to (a) use the custom claims in the rules and (b) use granular access rights.

How to set up Firebase firestore security to a collection per user/group?

I plan to have a (sub-)collection of Firestore db documents per user (I'm also using firebase auth) in my Javascript web app. How do I set up Firestore security rules so each user can only see her own docs? (But admin users can see all)
Actually my real use case a little more complicated, since I want all users who are part of group X to be able to access group X's documents. So I think I need to be able to do a db query on the group somehow.

Firebase Auth - list of users by Custom Claims

We're using Custom Claims in Firebase Auth to manage access in Firebase and allow custom UI experience.
We're now working on the User Admin part and we can't find a way to retrieve users by the custom claims.
For example, a user belongs to an organisation and have different access level - admin or user.
We need to allow the organisation's admin to see all their organisation's users.
We'd like to avoid using a separate database to manage organisation users to not double up the data.
Is it possible?
Can we retrieve all Auth users while filtering them by specific Custom Claim?
Thanks!
There is no built-in API to get a list of all users that have a specific claim.
There is an API to get a list of all users in the Admin SDK. So you could use that and then filter the users that have the claim you're looking for.
But performance of this will not be spectacular. So if listing all users with a specific claim is a common use-case for your app, you'll want to reconsider your concern about using an additional place to store the data. Duplicating data to achieve (well performing) use-cases is quite common in NoSQL.

firestore rules with custom functions error

I'm trying to create a rule to allow certain functions in my firestore db (I worked with firebase since 3 months ago, I'm really new in this). The main idea is to find the _key of the user profile that I have stored in an user document. Then, search all the permissions allowed in the user-profiles document with the id that I found before. If I found the permission, I'd get access to the function
service cloud.firestore {
match /databases/{database}/documents {
function getPermisos(idPermiso){
return get(/databases/{database}/documents/user-profiles/$(idPermiso)).data;
}
function getUserType(){
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.profile._key ;
}
match /proyectos/{document=**}{
allow list,get: if getPermisos(getUserType()).acessList.proyectos==true;
}
}
}
Is this the right way to make it? Thanks in advance for any help
You don't query rules for accessibility. You can define a collection of accessLevels where each document represents your various access types such as "admins", "groupone", "grouptwo", etc. see documentation
Control Access with Custom Claims and Security Rules
The Firebase Admin SDK supports defining custom attributes on user accounts. This
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.
User roles can be defined for the following common cases:
Giving a user administrative privileges to access data and resources.
Defining different groups that a user belongs to.
Providing multi-level access:
Differentiating paid/unpaid subscribers.
Differentiating moderators from regular users.
Teacher/student application, etc.
Add an additional identifier on a user. For example, a Firebase user could map to a different UID in another system.

Firestore - security rules for users within companies

Our current Firestore structure is as follows:
Currently we are not using any subcollections
Users have list of companies to which they belong
Every project is connected only with 1 company
Project belongs to a company, when in companyId field is written that company UID
My 1st question is how we can specify security rules defined by this database? Is there some best practice approach?
Our first idea was to do this:
match /databases/{database}/documents/projects/{projectUid}/{document=**} {
allow read: if
(/databases/$(database)/documents/projects/$(projectUid)/companyId) ===
(/databases/$(database)/documents/users/$(request.auth.uid)/companyId)
}
But according to the documentation this would mean that we would have for each read basically 3 reads (2 queries for security and 1 real read from DB). This seems like a waste of queries.
Is there a better approach than this?
We were thinking about changing to subcollections:
at the end we would have in root collections 'companies' and 'users' (to store all users details)
projects would be subcollection of companies
pages would be subcollection of projects
...etc
and companies would contain list of users (not the other way around like now) - but only list, not user details
This way we can use similar approach as from the doc, where each match would contain {companyId} and in allow statement we would use something like
match /databases/{database}/documents/companies/{companyId}/projects/{projectId} {
allow read: if
exists(/databases/$(database)/documents/companies/$(companyId)/users/$(request.auth.uid));
}
Thanks for any recommendations on how to build it in the most scalable and especially most secure way.
Have you considered adding a user's company ID as a custom claim to their profile? That way no additional reads are needed in your security rules.
Since setting these claims requires the Admin SDK, it will require that you can run trusted code somewhere. But if you don't have your own trusted environment yet, you could use Cloud Functions for that e.g. based on some other action like writes to your current Firestore structure.
Adding an answer to Frank.
Borrowing from other API SDKs such as microsoft graph, typically to make a resource request you start by initializing a Client object with an authentication token representing the scope/rights of the user. For example:
const client = new SDKClient(my_auth_token);
The client constructor would have a token validation step on claims. You can then make REST calls such as
const response = await client.someEndpoint({ method: 'POST', body: my_object });
I suggest rather than using the admin SDK for read/write to your firestore, you use the regular firebase nodejs client. To restrict access with security rules, pass a firebase JWT token into this custom SDKClient class with the token that you obtain from the header of your requests. In the constructor, initialize a new firebase 'app'. Because a regular firebase client is
subject to security rules, this will do what you're looking for.
Some example code has already been offered in this answer.
I should add that according to this firebase doc there is a 'warning' to use the admin-sdk server-side, but I'm not sure I see why.
One approach I've thought of for something similar that we are working on, that is, private chatrooms where only certain users have access, is to encrypt all messages with an on-server key, and only grant read access for that key to certain users. That way the extra read only has to occur one time, just when getting the key for the first time, then normal reads with no additional security rules are fine, as an attacker wouldn't be able to do anything with them since they are encrypted and they don't have access to the key.

Resources