Firestore schema that supports group write access - firebase

I'm trying to design a firestore schema that allows many groups of people to edit only their own group's documents, but anyone service-wide can read them. It doesn't appear possible with firestore's security rules setup.
Role-based access
Firebase supports role-based access control, but it applies service-wide to documents, and I wouldn't be able to support many groups.
Custom roles are created and assigned via the gcloud console, so I could not create new, dynamic custom roles for each group, in order for them to have their own groups.
Firestore triggers to copy documents
I considered using firestore triggers (onCreate, onUpdate, onDelete) to copy documents to other user's subcollections who are in the same group. The issue with doing that is it could create an endless loop of triggers since each member can update a document. You could in theory use a property set on a copied doc that could prevent that, but it feels a little kludgy and brittle.
Is there a best practice, or is this not possible with Firestore?

Firebase Authentication with Custom Claims might be a good fit for this use case. You can add a claim groupId and set it's value to the ID of group that user belongs to. If user can be a part of multiple groups then store an array of groupIds. Your can check if this groupId is included in user's claims in security rules.
You would have to store the groupId in every document so we know which group that doc belongs to. You can try the following rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collection/{docId} {
allow read, write: if resource.data.groupId == request.auth.token.groupId;
}
}
}
The above rules will allow users to read, write a document only if groupId in document matched groupId in their custom claim.
The custom claims can be changed using Firebase Admin SDK in a secure environment only so you might have to use a Cloud function/server to add users to their respective group.

Related

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

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.

Firestore public collection rules

I have a web app that is basically a list of public items, non authenticated users can search the database and list the search results from the collection. Until I started getting daily emails from Firebase about "insecure rules in my Firestore database", I thought everything was OK, but now I am doubting them.
Here are my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth.uid !=null;
}
}
}
I am new to Firebase/Firestore and trying to understand:
1- The data is meant to be public but getting emails from Firebase, are these rules actually an issue?
2- Although the data is meant to be public, it would suck if someone would just drop by and download entire collection in one go. Thus, is possible to restrict public access a little by somehow preventing access to the collections/documents unless the request comes from the web app/domain? Maybe some form of token? Or some other approach?
Right now, your rules allows public read access to all documents in your database, as well as write access to all documents to authenticated users. Firebase generally considers that insecure.
The global, recursive /{document=**} wildcard is kind of dangerous since it might apply to data that you didn't intend to be readable or writably. You should instead call out the specific names of the collections in individual rules. That way, if you create new collections, they will not be automatically included with the wildcard.
In general, your rules should be as specific as possible and not depend on a global recursive wildcard.

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.

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