Is there any way to do some kind of authorization that allows only people who recently requested a page from Firebase Hosting to be able to send an HTTP POST request to a Firestore db and have it go through?
My page is basically an HTML form that posts data to a Firestore page, though it would be nice if at least one had to speak with the server beforehand, as people do not have to log in to post information.
[EDIT]
Requirements:
Serve a static HTML form (with some Javascript included)
The contents of the HTML form should be posted to a firestore database only if the client actually requested a page from the server within a reasonable timeframe
In general, some external code that did not recently request the page should not be able to post data to the database. This is just a minor restriction to mitigate any "attacks" being too easy.
No concept of user or login
All requests should be done through REST as including the Firebase SDK is way too large for this small of a project
With Firebase Hosting, there is no out-of-the-box logging mechanism that would allow detecting if a user has previously requested a page. You need a more "sophisticated" approach.
I can see two possible approaches. (There might be other ones!)
Approach #1 Use two Cloud Functions to:
Serve the page via Firebase Hosting, see Serve dynamic content and host microservices with Cloud Functions
Write to Firestore, after you have verified the user has previously requested a page.
More details:
For the first part, you will not actually serve dynamic content (I understand you plan to serve static pages through Hosting), but because this page is served through a Cloud Function, you will be able to save a unique token (e.g. a Firestore doc ID or any other UUID value) in, for example, Firestore, before sending back the page content.
Then, for the second part (writing to Firestore), the Cloud Function will first check that there is a document with the doc ID previously generated in the Firestore database, and if it is the case, will allow the write to the database (from the Cloud Function).
So, in this case, both Cloud Functions need to be HTTPS ones. You may be interested by this article which details the drawbacks of writing to Firestore through a CF.
Approach #2 Use Firestore security rules for the check before writing.
Do the same than the previous solution for serving the static pages;
Write directly to Firestore and implement a security rule that checks for the existence of a Firestore document with the doc ID saved in point 1. See the exists method.
Related
Using the product warranty registration webpage, users who purchase my product register for a warranty. The data entered by the users are written into Firestore.
However, while registering for a warranty they don't have to login or authenticate.
Is there any way to enforce security rules such a way that users entering data only on my webpage are allowed? (CORS header based etc)
The Firebase Rules do not really care about CORS or any headers in your request. The ideal here would be to enforce authentication to make your data secure, in fact if you check this documentation, open access is considered as a not insecure Firebase Rule.
If this is not an option for you, you can try setting up a middle man that handle this specific request to Firestore that can handle CORS and request headers with some logic and that makes the actual transaction with the Firestore, for this purpose I would recommend creating a HTTP Cloud Function.
That way you can add some logic behind your request handling and actually close the access to Firestore for external users with a retrict Firebase Rule, since Cloud Functions won't be subject to those. This will however create some extra costs to your project because of the use of a Cloud Function.
I already use ReCAPTCHA for Android apps client-side (I've also implemented, of course, its server-side verification).
However, this ReCAPTCHA is implemented only in one activity. But, of course, hackers can modify the app. For example:
they can simply remove ReCAPTCHA from all activities,
or start another activity that would not have ReCAPTCHA implemented; it's the case btw: I didn't implement ReCAPTCHA in each activity because it's useless according to the first problem I've just mentioned.
So I would want to detect bot and spam requests in Cloud Functions, then in Cloud Firestore, then in Cloud Storage, for the following accesses: read, write, function call. It'd allow me to prevent unwanted contents from being saved in Firestore for example (spamming messages, etc.), and to avoid overreaching my monthly billing quota (because of spam requests to Firestore for example).
Is it possible? How?
There is no "spam detection" for these products. Your security rules will determine who can access what data. If you don't have security rules in place, and allow public access, then anyone will be able to get that data, and you will be charged for it when that happens. This is the nature of publicly accessible cloud services.
If you want more control over the data in these products, you could stop all direct public access with security rules, and force clients to go through a backend you control. The backend could try to apply some logic to determine if it's "spam", by whatever criteria you determine. There is no simple algorithm for this - you will need to define what "spam" means, and reject the request if it meets you criteria.
Google does have some amount of abuse detection for its cloud products, but it will likely take a lot of abuse to trigger an alert. If you suspect abusive behavior, be sure to collect information and send that to Firebase support for assistance.
Just thought I'd add that there is another way to restrict access to Cloud Functions.
Doug already described Way 1, where you write the access logic within the cloud function. In that case, the function still gets invoked, but which code path is taken is up to your logic.
Way 2 is that you can set a function to be "private" so that it can't be invoked except by registered users (you decide on permissions). In this case, unauthenticated requests are denied and the function is not invoked at all.
Way 2 works because every Firebase project is also a Google Cloud Platform project, and GCP offers this functionality. Here are the relevant references to (a) Configuring functions as public/private, and then (b) authenticating end-users to your functions.
I know there are several questions regarding this (e.g. https://stackoverflow.com/a/52808572/3481904), but I still don't have a good solution for my case.
My application has Groups, which are created/removed dynamically, and members (users) can be added/removed at anytime.
Each Group has 0..N private files (Firebase Storage), saved in different paths (all having the prefix groups/{groupId}/...).
In Firestore Security Rules, I use get() & exists() to know if the signed-in-user is part of a group. But I cannot do this in the Firebase Storage Security Rules.
The 2 proposed solution are:
User Claims:
but the token needs to be refreshed (signing out/in, or renewing expired token) which is not acceptable for my use case, because users need to have access immediately once invited. Also, a user can be part of many groups, which can potentially grow over 1000 bytes.
File Metadata:
but Groups can have N files in different paths, so I will need to loop-list all files of a group, and set the userIds of the group-members in the metadata of each file, allowing access to it. This would be an action triggered by Firestore (a Firebase Function), when a member is added/removed.
I don't like this approach because:
needs to loop-list N files and set metadata for each one (not very performant)
To add new files, I think I would need to set create to public (as there is no metadata to check against yet), and then a Function would need to be triggered to add the userIds to the metadata
there might be some seconds of delay to give files access, which could cause problems in my case if the user opens the group page before that time, having a bad experience
So, my questions are:
Is there a better way?
If I only allow the client to get and create all files when authenticated (disallowing delete and list), would this be enough for security? I think that there might be a chance that malicious hackers can upload anything with an anonymous user, or potentially read all private group files if they know the path...
Thanks!
If custom claims don't work for you, there is really no "good" way to implement this. Your only real options are:
Make use of Cloud Functions in some way to mirror the relevant data from Firestore into Storage, placing Firestore document data into Storage object metadata to be checked by rules.
Route all access to Storage through a backend you control (could also be Cloud Functions) that performs all the relevant security checks. If you use Cloud Functions, this will not work for files whose content is greater than 10MB, as that's the limit for the size of the request and response with Cloud Functions.
Please file a feature request with Firebase support to be allow use of Firestore documents in Storage rules - it's a common request. https://support.google.com/firebase/contact/support
I had similar use case, here’s another way to go about it without using file metadata.
Create a private bucket
Upload files to this bucket via cloud function
2a. validate group stuff here then upload to above bucket.
2b. Generate a signed url for uploaded file
2c. Put this signed URL in Firestore where only the group members can read it (eg. /groups/id/urls)
In UI get the signed URL from firestore for given image id in a group and render the image.
Because we generate the signed URL and upload file together there will be no delay in using the image. (The upload might take longer but we can show spinner)
Also we generate the URL once so not incurring any B class operations or extra functions running every time we add new members to groups.
If you want to be more secure you could set expiry of signed urls quite short and rotate them periodically.
I want to respond to Firebase events to generate (keep updated) to generate HTML pages and put them to Firebase Hosting so that they can be immediately available for use. I have it working except for the part about uploading the resulting HTML to Firebase hosting. It seems like I cannot do it this way but I want to so that all the pages are pre-rendered and ready to load fast.
I have cloud functions connected to hosting but that is the same old way of fetching from the database during a request cycle which I wanted to avoid.
On this page it says "Prerender your single-page apps to improve SEO." and thats what I want. Is it possible? How to store the pre-rendered pages from a HTTP function?
The "Prerender your single-page apps to improve SEO." talked about on that page is prerender in the cloud before serving the content to the requesting party. It is not generate static files when data updates before a request is even made.Generally the prerendering with appropriate caching headers is enough for most use-cases.
If you really want to pregenerate all the pages whenever data changes, you could do that but that'll be more complicated. There are some good articles and guides about deploying to Firebase Hosting after continuous integration finishes. The general idea holds true for what it sounds like you want except what triggers the build/deploy is data driven rather than code change.
The way to pre-render HTML so that metadata such as JSON-LD is available to search engines and opengraph is available to social media platforms for rich cards in shared links is to use Cloud Functions. You basically run Express/Pug (previously Jade) in your cloud function(s) to respond with HTML after whatever database/datastore lookups have completed. I've implemented this and it works great.
Call functions via HTTP requests provides some direction. You basically add some forwarding info to customize your hosting. This will direct HTTP calls over to your Express server running in Cloud Functions. Check the firebase functions github repo for sample code.
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.