I am building a timetable system using Firestore as a database (and Angular v9.). I need a way to store only VALID data in the database.
I realize that with Firestore rules, I can set rules for providing access to the user to read or write. But how do I check if the value being stored is correct or VALID? For example, in my case, check if the slot is already assigned. So that when a new slot is being added, it doesn't overlap...
Do I need a server or a proxy or cloud functions? Or is it possible in Firestore directly? Or perform the validation in the frontend only? I believe that checking the data in the frontend would be the worst way to do so...
If the validations you're trying to perform can't be done in security rules, then you will need to provide your own backend the exposes APIs for your app to use. Instead of adding the data directly to the database, it would send the document data for the backend to validate and add. You can certainly use Cloud Functions for this (and a full discussion is beyond the scope of Stack Overflow answer), but you have essentially unlimited options here.
Related
I am setting up my app so that any client device can only impact collections/sub-collections that they own. However, to interact with other users, a user will need to make the app create a row in another user's collection. What is the safest way to do this?
My idea for this would be to have the app call a cloud function to create the record in the other user's collection. The cloud function would read the request and make sure of the following:
The incoming request has an existing UID
The incoming request's user's email is verified
The incoming request's UID has a record in the Firestore 'users' collection
If I do this, is this just as secure as using Firestore security rules?
The question you're asking is unfortunately not as easy to answer as you expect. Firestore security rules don't ensure general "security" of your app any more than backend code. Rules let you specify rules for reads and writes according to the conditions you provide, if you want to use them. If rules are not sufficient for the requirements at hand, then maybe backend code will work better. In either case, you can allow or deny access based on conditions you provide.
In terms of functionality, both options allow you to allow or restrict access in different ways. Neither one is more or less "secure" than the other. The main issue you should consider is which one lets you most easily specify those rules. Security rules are fundamentally more limited in what you can check, while backend code is fundamentally more flexible. The option you choose is dependent on what you're trying to allow or reject.
The constraints you specified in the question could be enforced by either security rules or backend code, so I don't see that one is necessarily more or less secure than the other.
Firebase Firestore: How to monitor read document count by collection?
So first of something similar like question was already asked almost a year ago so dont mark it duplicate cause I need some suggestions in detail.
So here is the scenario,
lets say I have some collections of database and in future I might need to perform some ML on the DB. According to the documents visit.
That is how many times a specific document is visited and for how much time.
I know the mentioned solution above indirectly suggests to perform a read followed by write operation to the database to append the read count every time I visit the database. But it seems this needs to be done from client side
Now if you see, lets say I have some documents and client is only allowed to read the document and not given with access for writing or updating. In this case either I will have to maintain a separate collection specifically to maintain the count, which is of course from client side or else I will have to expose a specific field in the parent document (actual documents from where I am showing the data to clients) to be write enabled and rest remaining field protected.
But fecthing this data from client side sounds an alarm for lot of things and parameters cause I want to collect this data even if the client is not authenticated.
I saw the documentation of cloud functions and it seems there is not trigger function which works as a watch dog for listening if the document is being fetched.
So I want some suggestions on how can we perform this in GCP by creating own custom trigger or hook in a server.
Just a head start will be so usefull.
You cannot keep track of read counts if you are using the Client SDKs. You would have to fetch data from Firestore with some secure env in the middle (Cloud Functions or your own server).
A callable function like this may be useful:
// Returns data for the path passed in data obj
exports.getData = functions.https.onCall(async (data, context) => {
const snapshot = admin.firestore().doc(data.path).get()
//Increment the read count
await admin.firestore().collection("uesrs").doc(context.auth.uid).update({
reads: admin.firestore.FieldValue.increment(1)
})
return snapshot.data()
});
Do note that you are using Firebase Admin SDK in this case which has complete access to all Firebase resources (bypasses all security rules). So you'll need to authorize the user yourself. You can get UID of user calling the function like this: context.auth.uid and then maybe some simple if-else logic will help.
One solution would be to use a Cloud Function in order to read or write from/to Firestore instead of directly interacting with Firestore from you front-end (with one of the Client SDKs).
This way you can keep one or more counters of the number of reads as well as calculate and apply specific access rights and everything is done in the back-end, not in the front-end. With a Callable Cloud Function you can get the user ID of authenticated users out of the box.
Note that by going through a Cloud Function you will loose some advantages of the Client SDKs, for example the ability to use a listener for real-time updates or the possibility to declare access rights through standard security rules. The following article covers the advantages and drawbacks of such approach.
I am creating a react native application using Firestore and I am not sure how to implement secure schema validation on document creation and update.
If I understand security rules, it is possible to:
Limit who can perform operations (update, read, write, etc.) on documents
Limit operations allowed based on field conditionals
Limit operations allowed based on custom functions (post w/ examples)
My concern is that because of the client side nature of the requests, a savvy user could utilize their authentication and some client side code to .set() any field or map/object to any value they want unless a security rule prevents it. It appears I could use very complicated custom functions to validate the data received. I could also validate every update and create through a Cloud Function API, but I am attempting to use the Firestore database itself whenever possible.
Am I right to be concerned about the potential for users to abuse their .set() field creation abilities on authorized documents (i.e. documents with minimal userId rules)?
Is there an accepted way to create security rules that prevent client abuse of documents that don't have custom functions that validate the schema?
You should always consider malicious users, and how they might affect your data, no matter whether you write the validation in security rules or in more traditional code in Cloud Functions.
Compare these two statements from your question:
"I could use very complicated custom functions to validate the data received"
"I could also validate every update and create through a Cloud Function API"
In both cases you're writing custom code to ensure the data the user enters is valid according to your business rules. Since these rules are specific to your business, there's no way to prevent you having to write them. The only difference is where you write these business rules. With Cloud Functions you're writing the validations in regular JavaScript code, in an environment you may already be familiar with. With security rules you're writing the validations in a domain-specific language, which you'll have to learn.
I personally far prefer writing my business rules into Firestore's server-side security rule language, and then use Cloud Functions for implementing business logic on top of that validated data.
If you are worried that user might just reverse engineer your app and mess up your code to harm your database, then yes this is possible. You should have proper rules set. Talking of updating data in database from app, try to update it through cloud functions as far as possible. This way you might need to give less access to your users to the database directly.
You can check my answer here. This will help you setting rules and some ways to code adapt your app code based on situation. The answer also has some lines on where can one use cloud functions to reduce direct contact with the database.
And if there is no know or you feel the information should be directly updated to the database, change your rules to this: ".write": "$uid === auth.uid" .
Here $uid is name of parent node and can be anything else. This way a user can access his/her data only and even if the user modifies your app, they can harm their data only. (You should have correct rules set).
You can check out this link for most of the rules combinations.
And do check the answer whose link is above. That might clarify how it will secure your database to some extent. If you can provide any particular situation regarding your app and want some information for how to set rules there, feel free to drop a comment :-)
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.
Let's say I'm developing app like Instagram: for iOS, Android and Web. I decided to use Google Firebase as it really seems to simplify the work.
The features user needs in the app are:
Authorization/Registration
Uploading photos
Searching for other people, following them and see their photos
I come from traditional "own-backend" development where I do need to setup a server, create database and finally write the API to let the frontend retrieve the data from the server. That's the reason why it's unclear to me how it all works in Firebase.
So the question is how can I create such app:
Should I create my own API with cloud functions? Or it's ok to work with the database directly from the client-side?
If I work with the database directly why do I need cloud functions? Should I use them?
Sorry for such silly questions, but it is really hard to get from scratch.
The main difference between Firebase and the traditional setup you describe is that with Firebase, as far as the app developer is concerned, the client has direct access to the database, without the need for an intermediate custom API layer. Firebase provides SDKs in various languages that you would typically use to fetch the data you need / commit data updates.
You also have admin SDKs that you can use server-side, but these are meant for you to run some custom business logic - such as analytics, caching in an external service, for exemple - not for you to implement a data fetching API layer.
This has 2 important consequences:
You must define security rules to control who is allowed to read/write at what paths in your database. These security rules are defined at the project level, and rely on the authenticated user (using Firebase Authentication). Typically, if you store the user profile at the path users/$userId, you would define a rule saying that this node can be written to only if the authenticated user has an id of $userId.
You must structure your data in a way that makes it easily readable - without the need for complex database operations such as JOINs that are not supported by Firebase (you do have some limited querying options tough).
These 2 points allow you to skip the 2 main roles of traditional APIs: validating access and fetching/formatting the data.
Cloud functions allow you to react to data changes. Let's say everytime a new user is created, you want to send him a Welcome email: you could define a cloud function sending this email everytime a new node is appended to the users path. They allow you to run the code you would typically run server-side when writes happen, so they can have a very broad range of use-cases: side-effects (such as sending an email), caching data in an external service, caching data within Firebase for easier reads, analytics, etc..
You don't really need a server, you can access the database directly from the client, as long as your users are authenticated and you have defined reasonable security rules on Firebase.
In your use case you could, for example, use cloud functions to create a thumbnail when someone uploads a photo (Firebase Cloud Functions has ImageMagick included for that), or to denormalize your data so your application is faster, or to generate logs. So, basically you can use them whenever you need to do some server side processing when something changes on your database or storage. But I find cloud functions hard to develop and debug, and there are alternatives such as creating a Node application that subscribes to real time changes in your data and processes it. The downside is that you need to host it outside Firebase.
My answer is definitely NOT complete or professional, but here are the reasons why I choose Cloud Functions
Performance
You mentioned that you're writing an instagram-like mobile device app, then I assume that people can comment on others' pictures, as well as view those comments. How would you like to download comments from database and display them on users' devices? I mean, there could be hundreds, maybe thousands of comments on 1 post, you'll need to paginate your results. Why not let the server do all the hard work, free up users' devices and wait for the results? This doesn't seem like a lot better, but let's face it, if your app is incredibly successful, you'll have millions of users, millions of comments that you need to deal with, server will do those hard jobs way better than a mobile phone.
Security
If your project is small, then it's true that you won't worry about performance, but what about security? If you do everything on client side, you're basically allowing every device to connect to your database, meaning that every device can read from/write into your database. Once a malicious user have found out your database url, all he has to do is to
firebase.database().ref(...).remove();
With 1 line of code, you'll lose all your data. Okay, if you say, then I'll just come up with some good security rules like the one below:
This means that for each post, only the owner of that post can make any changes to it or read from it, other people are forbidden to do anything. It's good, but not realistic. People are supposed to be able to comment on the post, that's modifying the post, this rule will not apply to the situation. But again, if you let everybody read/write, it's not safe again. Then, why not just make .read and .write false, like this:
It's 100% safe, because nobody can do anything about anything in your database. Then, you write an API to do all the operations to your database. API limits the operations that can be done to your database. And you have experience in writing APIs, I'm sure you can do something to make your API strong in terms of security, for example, if a user wants to delete a post that he created, in your deletePost API, you're supposed to authenticate the user first. This way, 'nobody' can cause any damage to your database.