I have read the Firebase docs, and am wondering how Firebase rules affect a user's experience within an app. Let's say you can only access a group if you are apart of that group as per your Firestore rules. So would you instead of querying all groups where member.uid == auth.uid ( like you would without rules), would you just query the "groups" collection, and the user would only receive the groups that they are apart of?
Every time I read about Firestore rules, they pretty much give the same example, without giving a sample of what the user would see if they queried the database. If someone could point me in the right direction of this, or show how I could test something like this, that would be much appreciated.
So would you instead of querying all groups where member.uid == auth.uid ( like you would without rules), would you just query the "groups" collection, and the user would only receive the groups that they are apart of?
Firebase rules cannot filters data. You cannot have the client read an entire collection and then expect the rules to return a subset of the documents in that collection.
The reason for this is that listeners/observers are only validated once you attach them, after that they have access to the data. This is necessary in order to get the realtime update performance acceptable.
While you can't use rules to filter data, you can use them to validate queries. For an example of this, see the Firestore documentation on securely querying data:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth.uid == resource.data.author;
}
}
}
Which rejects this query:
// This query will fail
db.collection("stories").get()
But allows this one:
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
You can see that we're essentially repeating the condition from the code in the rules. This allows you to securely expose a subset of the documents in a collection based on (for example) the user's UID.
The firebase rules are there just to prevent spam in your application and also other stuff.
If you use read:true and write:true then anyone can read and write to the database, that is why it is better to use:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
This way only authenticated users can see the data that is being retrieved from the database or can send data to the database.
If you have a users in a group and you used this:
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'groups' collection
match /groups/{group} {
allow read, write: if true;
}
}
}
It means all members of that group can read from that database, so if you are in that group, then the data will appear in your application.
more info here:
https://firebase.google.com/docs/firestore/security/rules-structure
Related
I have a news application connected with Firebase and I add the news manually in Firestore only, inside 5 Collection, I am not good at using Firebase, I use the test mode and now I need your help in changing it, the application is for news, meaning no one can add in it only read the data from Everyone without even logging in, I get many security messages from Google, please write me the code here because I browsed a lot of questions here and I didn't understand anything and it seems that I need a course on Firebase security.
I use a pay-per-use plan, and I'm afraid of high bills. Please help me with a strong security that protects me from fake reading or even writing.
It is hard to answer without an understanding of your firestore collection names but here is a general solution.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// in this example, only admins can write or delete, but anyone can read.
match /news/{docId}{
allow read;
allow write, delete: if isAdmin(request);
}
// This is an example for a users collection, where the doc ID is the
// users authentication Id
// Note the userId=** -> this will apply rules to all subcollections
match /users/{userId=**}{
allow read: if isAuthenticated(request); // logged in users can read
// Only admins or the user with a matching doc Id can write.
allow write: if isAdmin(request) || isOwner(userId);
allow delete: if isAdmin(request); // only admins can delete
}
function isOwner(userID){
return request.auth.uid == userID;
}
function isAuthenticated(request){
return request.auth != null;
}
// this requires you to have set up custom claims
function isAdmin(request) {
return request.auth.token.admin
}
}
}
To learn more about protecting your collections with role based authentication, take a look at Custom Claims
From the Firebase Security Rules docs,
Remember that any time your rules include a read, like the rules below, you're billed for a read operation in Cloud Firestore.
service cloud.firestore {
match /databases/{database}/documents {
// For attribute-based access control, check for an admin claim
allow write: if request.auth.token.admin == true;
allow read: true;
// Alterntatively, for role-based access, assign specific roles to users
match /some_collection/{document} {
allow read: if request.auth.token.reader == "true";
allow write: if request.auth.token.writer == "true";
}
}
}
But I couldn't see any Firestore get operations like get(/databases/$(database)/documents/users/$(request.auth.uid)). So how this above rule is billed against firestore?
You are right: The rules in your question, which check custom claims, don’t incur Firestore reads.
It is most probably a copy/paste error in the documentation: If you look at the doc page, the block above the one you pasted in your question has the exact same sentence (and this time this sentence correctly applies to the code example, which includes the get() function).
(FYI I’ve used the feedback button at the bottom of the doc page to report the problem to the Firebase team)
Bad idea: I am going to allow anonymous website users to log a visit to a Firestore collection, but I don't want them stomping around and using my site to serve bad things™️, how do I constrain the writes?
(admittedly poor practice, this is just a prototype, the right way to do this would be to hit up a middle layer, have the middle layer validate the write request, only allow the firestore DB to be written to from the middle layer, etc.)
I'd like to constrain the documents that the visitor can add to the collection to a simple "add a doc with a number and timestamp."
Something like
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if
request.time < timestamp.date(2021, 5, 22);
}
match /{pageId}/visit/} {
allow write: if
request.time < timestamp.date(2021, 5, 22)
&& request.num EXISTS AS A NUMBER
&& request.timestamp EXISTS AS A TIMESTAMP
&& NO OTHER FUNNY STUFF ALLOWED;
}
}
}
Have a look at my answer here to learn how to implement both a global and a user-specific write rate limit: How do I implement a write rate limit in Cloud Firestore security rules?
In addition I would recommend to anonymously authenticate the users, so that users are signed in automatically without enter credentials. That would then allow you to limit the write rate limit per user, as my answer to the above question also shows.
Here are my rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{id}{
allow read : if (request.auth.uid == resource.data.uid);
allow write : if false;
}
match /Class/{id}{
allow read : if (request.auth.uid == resource.data.instructor.uid || (request.auth.uid == resource.data.admin.uid));
allow write : if false;
}
match /Class/{id}/Topics/{doc} {
allow read:
if request.auth.uid == resource.data.topicOwnerUID || request.auth.uid == resource.data.adminUID
allow write: if false;
}
}
}
Everything is fine except trying to pull all the topics from Class/docs/Topics/docs. I use getDocuments and try to get all documents using limit(50), I only have 1 topic now, but I also tried making limit to 1 and still don't work.. In each of the Topics docs, there is an adminUID and a topicOwnerUID field. When the owner tries to pull all documents I am getting an error: "Error: Missing or insufficient permissions.". I checked and all the required fields are there and this should allow.
Edit: seems like if i remove .limit and just get a single doc with a specific docID it works. But this isn't what I want :/. Maybe I will have to make the subcollection into its own collection.
Edit2: doesn't work even if it is not a subcollection. completely lost rn. Looks like the only way is to get each doc individually but this ruins lazyLoading. Firebase is lagging behind. will be switching to another db.
Security rules do not mean anything without the matching queries - remember that Security Rules ARE NOT FILTERS - they will NOT "just give you the records that are allowed" - you MUST use queries to match your rules. If your query could return a document that isn't allowed by the rules, then the ENTIRE query is disallowed.
I can tell you from EXTENSIVE use that Firebase/Firestore do not lag behind in any way, and I use complex queries continuously.
I have an angular app connected to a Cloud Firestore and I am still trying to work out how to specify the rules. As of now I have the use case where a user is a kitchen (this is an app for a student dorm), which means that there can be X number of users, one for each kitchen, and I have the following rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
//Only allow authenticated users to read
allow read: if request.auth != null;
}
match /kitchens/{userId}/{document=**} {
// Only the kitchen that is logged in can write to its own data
allow write: if request.auth.uid == userId;
}
}
}
The thing is that since there is a certain number of kitchens, the register screen lets the user choose which kitchen that wants to register from a dropdown list. The list is then filtered based on which kitchen id's that have not already been registered, so I need permission to read the kitchen id's. I just don't want anyone but the kitchens themselves to read the data that belongs to that kitchen. I tried with:
match /kitchens/{id=**} {
// Unauthenticated users can read kitchens
allow read: if true;
}
But sending a http request with Postman just gives me all that I asks for that exists under the kitchen.
So is there a way to only be able to read the fields of the root document and not any of the collections contained in the root, so I can see which kitchens that are already registered, but not e.g. the residents of the kitchen?
This part of the rules is causing you trouble:
match /{document=**} {
//Only allow authenticated users to read
allow read: if request.auth != null;
}
You might think that it's only affecting the "root" documents, but it's actually affecting all documents. That ** will match any document at any depth. With this rule in place, any authenticated user can read every single document in the database.
You should remove this rule and replace it with one that more specifically addresses your requirements.
You have a similar problem here:
match /kitchens/{id=**} {
// Unauthenticated users can read kitchens
allow read: if true;
}
This allows anyone at at all to read the kitchens collection, and *all** of the nested documents in subcollections. Again, that's how the ** wildcard works.
If you want to restrict a match to just a single collection, don't use these "greedy" wildcards. Just use a normal one that only matches documents in the immediate subcollection:
match /kitchens/{id} {
// Unauthenticated users can read kitchens
allow read: if true;
}
You might want to go over the documentation about recursive wildcards.