Firebase storage authentication - flutter

I want to restrict access to Firebase Storage objects with storage rules and custom claims on authenticated users. Both cool features, good for scaling.
My problem however is:
The Firebase Storage download link allows public access, no matter the rules.
My download link given by getDownloadURL() is:
https://firebasestorage.googleapis.com/v0/b/***myappname***.appspot.com/o/logos%2F1618740110634.png?alt=media&token=bdf6a5c5-54a2-4211-aa40-85177a38210a
My rules are:
match /{allPaths=**} {
allow read, write: if false;
}
What link then should I use to restrict access to authenticated users only and for checking custom claims with for my admin (excel reports) files? I am very confused.
Have tried direct links, without the token at the end, the given storage location link.
With the public link, anyone has access I don't want them to have.
Using Flutter mobile and web.

The getDownloadURL() always returns a public URL. Everyone who has it can access the file.
There are short lived signed token URLs but they are not supported on native device SDKs.
The downloadURL is very secure. If someon does not have it no one will get to your file. So the magic here is to not share it anywere and to get what you would like work only with references. Only if someone has access to your reference (according to the storage rules) he can generate the downloadURL.
I would recommend to work in your app only with those references and only get the downloadURL when you realy want to access the file in App or open it.
In the firebase storage rules you can then use auth and the customClaims to define who can access the file references.
Once a downloadURL is generated the firebase storage rules don't matter. The file will be accessible over that link.

Related

Firebase RealTime Database Security Rules for personal app

I have created an application in Flutter and I will be the only one to use it since it makes my work easier. It uses Firebase Realtime Database to synchronize data between my devices. When I read the Firebase documentation, I realized I needed to protect my database to prevent access from strangers, so I looked for a way to import some kind of password to pass as a payload when requesting and writing data. But there doesn't seem to be anything like that, I would have to implement Firebase Auth to do that as well. So I opted to create my own dataset with a very particular name, and set the read and write rules only to that particular path. My rules look like this:
{
"rules": {
"dataset-verylong32charstringwithalphanumericvalue":{
".read": "true",
".write": "true",
}
}
}
So in theory any other access attempts should be blocked. Since this is a bit of an odd method and not described in the documentation. Can I consider this method safe?
Obviously I know that if some malicious person gets wind of my string they will have full access to my data, but since the chances are low of that happening, I just needed superficial protection against abuse of the service
I have tried making REST requests and all attempts seem to be blocked.
So I expect it to be secure. However, I fear there may be a method to map all the paths in my database and then easily derive my string
What you're using is known as a shared secret. If it meets your needs then that's a valid way of securing access to the data. There is no way through the client-side SDKs and API to read the root of the database, and thus to learn your secret path that way.
For example, download URLs generated by Firebase for Cloud Storage depend on a shared secret to make files publicly readable by everyone who has that secret (it's the token parameter in the URL).
I also used this approach myself when dealing with the data for an events web site. The site was statically regenerated when the data in the database changed, so the shared secret never ended up in the published site.
The problem you need to figure out is how you're going to get the shared secret to the consumers of the data. In the above examples we expect either everyone to at possibly get the secret, but then not do any hard (since download URLs are read-only); or we expect only trusted services to know the secret and it never to reach anyone else. If your use-case is different than these, finding a way to share the secret out of band may become your next problem to solve.
A simple alternative is to implement anonymous authentication, which allows the app to sign in without requiring any credentials from the user and with a single line of code. With that you can then restrict access to the data to just the UID(s) that you know in your database's security rules. I usually hard-code the UID in the rules, until I get tired of adding/updating them, at which point I switch over to storing an allow-list of them in the database itself.
This is not a safe or efficient method.
If you are the only one who will use this app and can create user accounts, then you could just check if the user is authenticated or not, e.g.
service cloud.firestore {
match /databases/{database}/documents {
allow read, write, update, delete: if request.auth != null;
}
}
If you need to add another layer of security, then you can add custom user claims to the authentication token: https://firebase.google.com/docs/auth/admin/custom-claims
You can then securely access those extra claim fields in the rules (e.g. "request.auth.customField"), and compare them to some other data in your database.
Adding the claims is typically done on a secure backend to keep the Firebase admin details private - but if you are the only user on the frontend app(s), it shouldn't be a security concern to do it on the front end too.

Flutter App: Firebase Credentials viewable in Source Code (apk / web) = unsecure database? [duplicate]

The Firebase Web-App guide states I should put the given apiKey in my Html to initialize Firebase:
// TODO: Replace with your project's customized code snippet
<script src="https://www.gstatic.com/firebasejs/3.0.2/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>'
};
firebase.initializeApp(config);
</script>
By doing so, the apiKey is exposed to every visitor.
What is the purpose of that key and is it really meant to be public?
The apiKey in this configuration snippet just identifies your Firebase project on the Google servers. It is not a security risk for someone to know it. In fact, it is necessary for them to know it, in order for them to interact with your Firebase project. This same configuration data is also included in every iOS and Android app that uses Firebase as its backend.
In that sense it is very similar to the database URL that identifies the back-end database associated with your project in the same snippet: https://<app-id>.firebaseio.com. See this question on why this is not a security risk: How to restrict Firebase data modification?, including the use of Firebase's server side security rules to ensure only authorized users can access the backend services.
If you want to learn how to secure all data access to your Firebase backend services is authorized, read up on the documentation on Firebase security rules. These rules control access to file storage and database access, and are enforced on the Firebase servers. So no matter if it's your code, or somebody else's code that uses you configuration data, it can only do what the security rules allow it to do.
For another explanation of what Firebase uses these values for, and for which of them you can set quotas, see the Firebase documentation on using and managing API keys.
If you'd like to reduce the risk of committing this configuration data to version control, consider using the SDK auto-configuration of Firebase Hosting. While the keys will still end up in the browser in the same format, they won't be hard-coded into your code anymore with that.
Update (May 2021): Thanks to the new feature called Firebase App Check, it is now actually possible to limit access to the backend services in your Firebase project to only those coming from iOS, Android and Web apps that are registered in that specific project.
You'll typically want to combine this with the user authentication based security described above, so that you have another shield against abusive users that do use your app.
By combining App Check with security rules you have both broad protection against abuse, and fine gained control over what data each user can access, while still allowing direct access to the database from your client-side application code.
Building on the answers of prufrofro and Frank van Puffelen here, I put together this setup that doesn't prevent scraping, but can make it slightly harder to use your API key.
Warning: To get your data, even with this method, one can for example simply open the JS console in Chrome and type:
firebase.database().ref("/get/all/the/data").once("value", function (data) {
console.log(data.val());
});
Only the database security rules can protect your data.
Nevertheless, I restricted my production API key use to my domain name like this:
https://console.developers.google.com/apis
Select your Firebase project
Credentials
Under API keys, pick your Browser key. It should look like this: "Browser key (auto created by Google Service)"
In "Accept requests from these
HTTP referrers (web sites)", add the URL of your app (exemple: projectname.firebaseapp.com/* )
Now the app will only work on this specific domain name. So I created another API Key that will be private for localhost developement.
Click Create credentials > API Key
By default, as mentioned by Emmanuel Campos, Firebase only whitelists localhost and your Firebase hosting domain.
In order to make sure I don't publish the wrong API key by mistake, I use one of the following methods to automatically use the more restricted one in production.
Setup for Create-React-App
In /env.development:
REACT_APP_API_KEY=###dev-key###
In /env.production:
REACT_APP_API_KEY=###public-key###
In /src/index.js
const firebaseConfig = {
apiKey: process.env.REACT_APP_API_KEY,
// ...
};
I am not convinced to expose security/config keys to client. I would not call it secure, not because some one can steal all private information from first day, because someone can make excessive request, and drain your quota and make you owe to Google a lot of money.
You need to think about many concepts from restricting people not to access where they are not supposed to be, DOS attacks etc.
I would more prefer the client first will hit to your web server, there you put what ever first hand firewall, captcha , cloudflare, custom security in between the client and server, or between server and firebase and you are good to go. At least you can first stop suspect activity before it reaches to firebase. You will have much more flexibility.
I only see one good usage scenario for using client based config for internal usages. For example, you have internal domain, and you are pretty sure outsiders cannot access there, so you can setup environment like browser -> firebase type.
The API key exposure creates a vulnerability when user/password sign up is enabled. There is an open API endpoint that takes the API key and allows anyone to create a new user account. They then can use this new account to log in to your Firebase Auth protected app or use the SDK to auth with user/pass and run queries.
I've reported this to Google but they say it's working as intended.
If you can't disable user/password accounts you should do the following:
Create a cloud function to auto disable new users onCreate and create a new DB entry to manage their access.
Ex: MyUsers/{userId}/Access: 0
exports.addUser = functions.auth.user().onCreate(onAddUser);
exports.deleteUser = functions.auth.user().onDelete(onDeleteUser);
Update your rules to only allow reads for users with access > 1.
On the off chance the listener function doesn't disable the account fast enough then the read rules will prevent them from reading any data.
I believe once database rules are written accurately, it will be enough to protect your data. Moreover, there are guidelines that one can follow to structure your database accordingly. For example, making a UID node under users, and putting all under information under it. After that, you will need to implement a simple database rule as below
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
No other user will be able to read other users' data, moreover, domain policy will restrict requests coming from other domains.
One can read more about it on
Firebase Security rules
While the original question was answered (that the api key can be exposed - the protection of the data must be set from the DB rulles), I was also looking for a solution to restrict the access to specific parts of the DB.
So after reading this and some personal research about the possibilities, I came up with a slightly different approach to restrict data usage for unauthorised users:
I save my users in my DB too, under the same uid (and save the profile data in there). So i just set the db rules like this:
".read": "auth != null && root.child('/userdata/'+auth.uid+'/userRole').exists()",
".write": "auth != null && root.child('/userdata/'+auth.uid+'/userRole').exists()"
This way only a previous saved user can add new users in the DB so there is no way anyone without an account can do operations on DB.
Also adding new users is posible only if the user has a special role and edit only by admin or by that user itself (something like this):
"userdata": {
"$userId": {
".write": "$userId === auth.uid || root.child('/userdata/'+auth.uid+'/userRole').val() === 'superadmin'",
...
EXPOSURE OF API KEYS ISN'T A SECURITY RISK BUT ANYONE CAN PUT YOUR CREDENTIALS ON THEIR SITE.
Open api keys leads to attacks that can use a lot resources at firebase that will definitely cost your hard money.
You can always restrict you firebase project keys to domains / IP's.
https://console.cloud.google.com/apis/credentials/key
select your project Id and key and restrict it to Your Android/iOs/web App.
It is oky to include them, and special care is required only for Firebase ML or when using Firebase Authentication
API keys for Firebase are different from typical API keys:
Unlike how API keys are typically used, API keys for Firebase services are not used to control access to backend resources; that can only be done with Firebase Security Rules. Usually, you need to fastidiously guard API keys (for example, by using a vault service or setting the keys as environment variables); however, API keys for Firebase services are ok to include in code or checked-in config files.
Although API keys for Firebase services are safe to include in code, there are a few specific cases when you should enforce limits for your API key; for example, if you're using Firebase ML or using Firebase Authentication with the email/password sign-in method. Learn more about these cases later on this page.
For more informations, check the offical docs
I am making a blog website on github pages. I got an idea to embbed comments in the end of every blog page. I understand how firebase get and gives you data.
I have tested many times with project and even using console. I am totally disagree the saying vlit is vulnerable.
Believe me there is no issue of showing your api key publically if you have followed privacy steps recommend by firebase.
Go to https://console.developers.google.com/apis
and perfrom a security steup.
You should not expose this info. in public, specially api keys.
It may lead to a privacy leak.
Before making the website public you should hide it. You can do it in 2 or more ways
Complex coding/hiding
Simply put firebase SDK codes at bottom of your website or app thus firebase automatically does all works. you don't need to put API keys anywhere

Cloud Storage - Disabled Public Access Prevention, but Failed

Okay, I was using Flutter and Firebase to upload data into Cloud Storage. I gained the downloadURL which can be accessible on web if people know the URL. I had enabled Public Access Prevention in Google Cloud Storage Console based on this doc and chose Access Control Uniform for this on doc.
I also had added Security Rule in Firebase Cloud Storage, so only Users with certain custom token can use it. But, it seems useless as everyone can get its downloaded URL. My question is why is that I still able to access the file if I am using the same URL which was I stored in Firestore? You can test it on this url.
Can hacker get the download URL I downloaded from Firestore?
Is there a secure way to download song from Firebase Cloud Storage so hacker won't get its URL?
Thank you for helping me out.
Updated v2:
I just found out that current audio file has its own AuthenticatedUrl as shown on this picture below. How can I get access to this url?
Updated v1:
I think I haven't activated Firebase App Check. Does this feature have ability to prevent it from being accessed publicly or maybe there is other things that I have to do to be able to prevent it being accessed publicly, beside all ways I described above???
Security rules only check if a user can get the download URL and do not restrict anyone from using it. You can use the getData() method instead. It doesn't return any URL and downloads the files directly and is controlled by security rules. So a user must be authenticated to fetch them.
As mentioned in the Answer :
If you're using the FlutterFire Storage library in your app, you can
call getData on a reference to the file to get its data. So with
that you just need to know the path to the data, and you won't need
the download URL in your application. Once you have the data locally,
you can create an image out of it with: Converting a byte array to
image in Flutter?
Unlike download URLs, the call to getData() is
checked by security rules, so you'll have to ensure that the user is
permitted to access the file.
You can also refer to this Answer :
For web apps: in the JavaScript/Web SDK using a download URL is the
only way to get at the data, while for the native mobile SDKs we also
have getData() and getFile() methods, which are enforced through
security rules.
Until that time, if signed URLs fit your needs
better, you can use those. Both signed URLs and download URLs are just
URLs that provide read-only access to the data. Signed URLs just
expire, while download URLs don't.
For more information, you can refer to this Github issue where a similar issue has been discussed.

How do you set object access from shared publicly to only being able to access the file momentarily?

I've been playing around in google cloud storage. I've been able to upload files without any signature mismatch errors by doing the following.
set cors using gsutils from a json file.
get signed urls with an additional 'x-goog-acl': 'project-private' key value (or at least I think I have)
and PUT my object to storage with an additional body object 'x-goog-acl': 'project-private'
It doesn't seem to do anything. When I look in the console my image file is still shared publicly.
What I'm trying to do is that the user that is authenticated in my web app is the only person that can access that file. How can I do that I thought it was ACL permissions but I'm not sure anymore.

Google storage external authorization

I need to store my service data in Google Storage and let my users download files depending on their (users) access rights.
I've already made service that connects to Google Storage using server-centric mechanism, and transfers them to client-side, but I need client-side to go to Storage and download file without server-side.
I've tried to use temporary links for files, but I can't check, if user downloaded file or not to properly delete temporary link.
I've tried to look for oauth2 support, but it seems Google doesn't support oauth in such way (When my service decides to allow access or no).
The best solution is to generate tokens for users and if Google Storage would call my service before every file download.
How can I achieve that?