Securely saving data in Firestore - firebase

The Firestore documentation shows examples of how to secure data using Firestore security rules based on the request.auth.uid field. These typically look something like this:
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;
}
}
}
That makes perfect sense.
What I don't understand (and doesn't appear to be shown anywhere) is how to set the resource.data.author field securely.
Obviously that can't just be based from the client because then any authenticated user can tamper with the request to set their author to any value.
I thought maybe we are supposed to use CloudFunctions to set that field but at the moment this doesn't work.
The impact of this is pretty clear in the role based access example:
{
user: "alice",
content: "I think this is a great story!"
}
Surely there must be a tamper-proof way to set the user field there - otherwise any user can make their comments appear to be from anyone else. This seems bad.
In the Firestore example web app, it seems to set the userId field on the client side and I think it is doing the same in the Android version.
What am I missing?
Edit: as #imjared points out this rule implies that 'alice' in user: "alice" is actually a uid, so I think this is safe.
I knew I was missing something.
match /comments/{comment} {
allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter', 'reader']);
allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter'])
&& request.resource.data.user == request.auth.uid;

When the user writes a document to Firebase, they can indeed send any value for the author field they want. But there's no way for them to set request.auth.uid. This last bit in crucial to ensure all (read and write) access is authorized.
The first rules snippet you shared actually has two rules, and it might be easier to separate them out for a moment:
allow read: if request.auth.uid == resource.data.author;
allow write: if request.auth.uid == resource.data.author;
The write rule only allows the operation when the author specific in the request is the same as the request.auth.uid. Since request.auth.uid can't be spoofed, and the value of author will only be accepted if it is the same, the write operation is only allowed if the author field is that of the currently authenticated user.
In fact, that latter rule is more regularly written as:
allow write: if request.auth.uid == request.resource.data.author;
The difference when using request is that it explicitly refers to the document (resource) that is in the write request. The result is the same here whether we use resource or request.resource, but I find it easier to see how security works when thinking of the request here.

Related

Best Practice to Store Ownership of a Document in Firestore

Let's say I have a collection todos that contains documents that represent todo lists of users.
To secure these documents, often, you can find the following snippets of security rules:
...
match /todos/{todo} {
allow create: if request.auth.uid != null && request.resource.data.ownedBy == request.auth.uid;
allow read, update, delete: if resource.data.ownedBy == request.auth.uid;
}
...
These rules allow CRUD operations on the documents as long as the ownedBy field is the same as the uid of the person performing the requests.
My concern here is that the ownedBy field is also part of that document, meaning that a user can easily modify ownedBy to a different userId. I doubt anyone will do it for any reasons, but from a developer point of view, would that mean it is dangerous to have the field you rely on to be part of the document that can be edited?
Another way to look at it is, this behavior is the same as storing the permissions/authorizations in the same documents. It'd be wrong to store { canEdit: true, canDelete: false} inside that same document, so why is it ok to store the ownedBy field in that document?
What are some good practices to deal with this problem?
"a user can easily modify ownedBy to a different userId"
Given your rules, they actually can't. You're explicitly checking that resource.data.ownedBy == request.auth.uid and request.resource.data.ownedBy == request.auth.uid. Given that request.auth is auto-populated by Firebase and can't be spoofed, the only value they can ever set for ownedBy is their own UID.
I also recommend checking out the Firebase documentation on controlling access per field.

What security rules should be applied to reads in Firebase?

I created a website for a theatre company with a list of their upcoming shows.
Thes dates are stored in firestore, so I put up some rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
}
match /{document=**} {
allow write: if request.auth != null;
}
}
}
I just want the admin to be able to write when they are logged in, but I want everybody to be able to access the database to read the dates.
I thaught it was ok, but I get a new email every day from firebase saying that the database is not secure because anybody can read my data.
What should I do?
Your security rules should allow exactly what your code requires and nothing more. This is known as the principle of least privilege, and is the best way to ensure malicious users can do no more than your own code already does.
Let's look at making reads and writes more secure in turn.
Securing reads
Your rules currently allow anyone to read the entire database. But your code doesn't read the entire database. It instead only reads a list of upcoming shows. So you should only allow reading of upcoming shows:
match /databases/{database}/documents {
match /upcoming_shows/{document} {
allow read: if true;
}
So now users can only read from a single collection: the one named upcoming_shows.
If you actually have a list of all shows, and your code only reads the upcoming shows by using a query, you could also secure that query so that someone reading all shows gets rejected.
As said at the start: your rules should only allow exactly what your code requires, and nothing more.
Securing writes
You said that only the administrator should be allowed to write data when they are logged in. But right now anybody who signs in to Firebase Authentication can write whatever they want in your entire database. So a malicious user can take the configuration from your application, call Firebase with that to sign in, and then for example delete all your data, add their own fake shows to it, or just create an entirely different data set in the database, that you then pay for.
There are two parts that you'll want to better secure:
Only the administrator can write.
They can only add shows.
Only the administrator can write
You know the administrator it seems, so you can probably find their UID in the Firebase console and simply hard-code that in your rules:
allow write: if request.auth == "uid of your known administrator";
Now with these rules, since Firebase determines the UID of the user and it can't be spoofed, you've ensured that only the one person that you identified can write to the database.
There are many variations of this pattern, but this is a good first step.
They can only add shows
With the above changes we already ensured that only the administrator can write, but they can still write whatever they want. The principle of least privilege dictates that we should ensure they can also only write what they must be able to write. In your case that is "adding new shows".
This again breaks down into two requirements:
The administrator can only write shows, meaning they can't write other types of data, or write data elsewhere in the database.
The administrator can only add shows, meaning they can't update or delete them.
Only allow writing of shows
The first requirement is two-fold once more, the first one being similar to what we did for reads: we want to ensure they can only write to the upcoming_shows collection:
match /upcoming_shows/{document} {
allow write: if request.auth == "uid of your known administrator";
}
The second part is that they can only write shows there, meaning you'll want to validate the data that they write. You'll want to only allow the fields that your code actually writes as (again) the rules should only allow exactly what the code does, and nothing more. This could include validating that the date of the show is in the future, if that is also something that your use-case requires.
Only allow adding shows, not updating/deleting them
Then finally your use-case says they can only add shows, which I read as not updating and/or deleting them. We can use granular rules to implement that requirement:
match /upcoming_shows/{document} {
allow create: if request.auth == "uid of your known administrator" &&
/* the validation rules from the previous step */;
}

Trying to display photos in app only to user that posted them using firestore rules

I have an app that user can login to and take and add photos to a feed. im using firestore as the database.
Right now ever user can see every other users photos as well as their own. I want only the user signed in to be able view their own photos.
A user in another forum suggested that I use firestore rules to set that control but I'm having trouble implementing to correct code.
I read through the documentation on firestore and trying to implement the code the way they have it is not working. Below is my database info and my current rules (which is not working) thank you.
Database:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**}{
allow read, write: if request.auth != null && request.auth.uid == 'byId';
}
}
I'm expecting this code to compare the uid of the user the the byId field (which should be the same) and allow them to read and write their own data if true
i also have an authentification page where uid == byId
This check request.auth.uid == 'byId' checks whether the UID of the current user is literally byId. If you want to check whether the UID is the same as ht value of the byId field in the document use:
if request.auth != null && request.auth.uid == request.resource.data.byId;
I also recommend checking out the Firebase documentation on securing document access.

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

Resources