Is it possible to prevent send custom id by rules in firebase - firebase

Is there any way to prevent user send custom id for newly created document?
For example
This operation create's new document with id "8pqLuAc6BXCVRF7SB6xT"
db.collection("users").add({foo : 1})
And in my application i always want to have id generate by firebase sdk not by user
because as we know user can hack app and trigger operation like this
db.collection("users").doc('CUSTOM_ID').set({foo : 1})
After this operation id will be "CUSTOM_ID" instead generated by SDK like '8pqLuAc6BXCVRF7SB6xT'
So question is how to write rule to ensure me that id is generated from sdk

Update following your clarification:
It is not possible with Security Rules to avoid a user specifying the ID of a document when it is created.
Even if the code in your front end only uses the add() method, one can write his own code and use the doc() method to write to your DB (as soon as he gets the Firebase configuration object, which is easy).
One possibility would be to write to Firestore via a Cloud Function, but this has some drawbacks see this article.
Original answer:
It is not crystal clear what you mean by a "custom id" but I understand that you probably want a given user to be able to create a document in the users collection only if this document ID is corresponding to his user uid (from the Auth service)
The following Security Rule should the trick:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow create: if request.auth != null && request.auth.uid == userId;
// ...
}
}
}

Related

Firestore Rules with multi-tenancy?

The Firebase Rules docs suggest building conditions comparing the authenticated user's token (i.e., request.auth) with the target Firestore document(s). Something like:
match /posts/{postId} {
allow read, write: if (request.auth.uid != null) &&
(resource.data.tenantId == request.auth.token.tenantId);
}
However, tenantId doesn't appear to be available in Firebase Rules like other related auth fields (e.g., uid, email, email_verified, etc.).
One option appears to be to add tenantId separately as a custom claim using the firebase-admin SDK. But that would create duplicate info on the user object:
{
uid: 'nzjNp3QIfSR6uWy',
emailVerified: true,
displayName: 'pickleR'
...
tenantId: 'wubalubadubdub',
customClaims: { tenantId: 'wubalubadubdub' },
}
An alternative option appears to be to create a tenants collection in Firestore. However, that approach seems to introduce needless complexity and inflate the # of required Firestore queries.
Are there alternatives for accessing the tenantId in Firestore Rules and/or alternative best practices for using Firestore with multi-tenancy?
Having gone down the custom claim route, I've then found the tenant Id is already stored in a nested object as 'request.auth.token.firebase.tenant'
In your example the rule would be:
match /posts/{postId} {
allow read, write: if (request.auth.uid != null) &&
(resource.data.tenantId == request.auth.token.firebase.tenant);
}
The two options you describe are the idiomatic ones:
Pass the information into the rules as part of the ID token as a custom claim.
Look up the information in the database from the rules by the request.auth.uid.
Neither of these is always better than the other: custom claims are more convenient and readable, while using the database is usually faster. It's common to use the database lookup for more volatile information, and claims for information that is "once".
Since this is for a tenant ID, which is unlikely to change quickly, I'd probably go for a custom claim.

Role based access to FireStore

I am trying to implement role based access to Firebase Firestore but although I took a look at Googles well done documentation I wasn't able to get it working.
I have set a string "role" in each users document. The document itself has the uid of the user.
My rule to access the users collection looks like this and the write rule must be wrong:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{document} {
allow read: if get(/databases/$(database)/documents/users/$(request.auth.id)/role/$(role)) == "Mitarbeiter"
}
}
}
XCode-console error:
[Firebase/Firestore][I-FST000001] Listen for query at users/EStQNC4cYcaSnKpxs4XnQf96brm2 failed: Missing or insufficient permissions.
The path of your document get() is looking inside a subcollection called "role", but you have none:
/users/$(request.auth.id)/role/$(role)
Also, you have no variable called "role" that we can see here.
Using what you've shown, it looks like the role you need is in the document itself, so there's no need for a get(). Just check it like this:
allow read: if resource.data.role == "Mitarbeiter";

Set createdBy field in document with current userId (auth.uid)

I know that Firebase has the FieldValue class, which can be used to generate e.g. a server-side timestamp when writing a document (link).
What's the preferred practice for inserting the current user's uid into a document?
Having the client provide that field seems to allow misuse - unless I provide a server rule that checks for (new/updated) documents to match the request.auth.uid, something like:
service cloud.firestore {
match /databases/{database}/documents {
match /broadcasts/{broadcast}/chatMessagesCollection/{message} {
allow write: if request.resource.data.uid == request.auth.uid;
allow read: if true;
}
}
}
I can't find anything on the web for the use-case of having a document be populated with the user writing it -- so what's the best take on this?
What you're doing now with security rules to enforce that the provided UID matches the current user is exactly the right thing to do. There is really nothing better (for this specific use case), and this is a common practice.
I've even written about it in this blog series: https://medium.com/firebase-developers/patterns-for-security-with-firebase-per-user-permissions-for-cloud-firestore-be67ee8edc4a

Firestore - disallow reading multiple documents at once

I've got a Firestore collection.
The IDs of the documents are secrets. You should be able to read only the document whose ID you know.
For the sake of simplicity. I'd like to stick to this approach.
However, by default, one can read an entire collection from Firestore, for example
await firestore.collection("secret_documents").get()
Is it possible to allow reading only one document at once, only when it's referred by its ID?
Yes, that is actually quite easy. To control what documents can be accessed, use Firebase security rules for Firestore.
By default your security rules will be read and write, but those can actually be broken down into more granular operations of get, list, create and update. And what you're trying to do is to allow get, but not a list operation. From the documentation:
service cloud.firestore {
match /databases/{database}/documents {
// A read rule can be divided into get and list rules
match /cities/{city} {
// Applies to single document read requests
allow get: if <condition>;
// Applies to queries and collection read requests
allow list: if <condition>;
}
...
So to allow get for everyone and disallow list calls:
allow get: if true;
allow list: if false;
You'll probably want to elaborate on the allow get rule a bit, because it's more common to restrict it, for example to users that are signed in to your project with Firebase Authentication:
allow get: if request.auth.uid != null;
https://firebase.google.com/docs/firestore/security/rules-query

Do I need to do a get for every field of a document I want to access

I see that to get a field of a document in security rules one must use get. The example below shows getting the 'admin' field of some document in the users collection. If I wanted to get another field, would I have to do another get request or can I just do one get request and get all the fields I need in the document.
Here is the example I'm referring to in the documentation.
https://firebase.google.com/docs/firestore/security/rules-conditions
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
Yes, you would have to write another get(). There are no variables in Firestore security rules, so you can't store the contents of a get() in order to use its data multiple times.
Multiple gets accessing the same document might not incur multiple read charges. The documentation states:
Some document access calls may be cached, and cached calls do not count towards the limits.

Resources