Related
I am making an application that will use Firebase realtime database for distribution of some data. I think my requirements are more or less common.
Few clients with dedicated app written in Java have read/write access.
Many mobile application users have read only access
Anyone else should have problems reading data (well, some problems)
Anyone else should not be able to read some parts of the data
Anyone else should not be able to write data (this is the most important)
While 3 is a desire, 4 is a strong desire, but 5 is an important requirement.
So far I see two approaches to this task
Give read and write access to authorized users, like
"rules": {
".read": "auth.uid !== null",
".write": "auth.uid !== null"
}
Automatically authenticate mobile users with anonymous login, authenticate writers with any other (OAuth) authentication.
Give read access to anyone, write access to authenticated users
"rules": {
".read": "true",
".write": "auth.uid !== null"
}
and encrypt the data or sensitive portions of it.
In any of these methods I see disadvantages. Method 1 is not good, because anyone who can hack the APK will be able to write to the database - this is unacceptable. Also Google will create hundreds or thousands of anonymous accounts for the database - one for each authorization attempt.
Method 2 avoids this, but there is another issue - anyone with good Internet connection will be able to download ALL the data, making me pay huge money to Google. Each terabyte of malicious download will cost me $1000. The only knowledge required to do this is the URL of the database. And in this case I will not be able to do anything, because all clients are already configured for an unrestricted read access.
Are my expectations correct, and what could be the right solution?
Your security rules should reflect precisely what you want your users to allow to do. In that sense they are part of your application code, just like the Java/Kotlin code is that runs on the Android devices.
My preferred model is to evolve my security rules as I'm writing application code.
So I initially grant nobody any access, as I have no code in my app yet that requires access.
Then as I for example write profile data from the app, my security rules reject that write. I modify my rules to allow only the new operation, and nothing else. So: a user can only write their own profile.
Next I may want all signed-in users to read each others' profile, so I write the code for that, which once again gets rejected. And then again, I modify my security rules to allow this specific read operation.
This approach leads to security rules that grant the minimal access that is needed, rather than your approach that tries to implement security as one big toggle for the entire database.
I highly recommend reading the Firebase documentation on security rules, specifically the page that contains some basic use-cases like the one I describe above
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
So I have this platform that has many security features that prevent interaction with the Firebase database, because the web version is very prone to tampering. The iOS version cannot interact with the database because of all these security features, as the security bypass functions cannot be called from the iOS end. How do I make it so that the iOS version of my platform can directly interact with the Firebase database? Is it something to do with perhaps authentication or database rules?
You need to change the rules of your realtime database follow the steps
Go to your Firebase Console
Select your project
Select Database
From Rules change them to :
{
"rules": {
".read": true,
".write": true
}
}
Another way in which your iOS version can interact with the database is to make the calls to the database in such a way that your calls abide by the rules.
For example name can only be set to string then in setValue make sure the name is passed as string. Make sure you write where you have write access. Don't try to write where the rule states that only read access is available.
There is no shortcut to write at a read only place unless the rule is changed or removed. If the rules require authentication then first authenticate the user and then try to interact with the db.
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
I'm trying to find the best way to design security for our app that uses firebase
Basic problem
We want our users’ data to be secure. We don’t want a malicious agent to be able to access other users’ private data on the Firebase db. It seems that there should be a solution for this via firebaseSimpleLogin, but despite scouring the documentation, we haven’t seen one.
Problem specifics
We offer an app with user accounts, and these users have private data
Users should only be able to read:
their own data
app-wide data relevant to all users, e.g. a template that all users get a copy of
when they initially create their account, the original copy of which
is on the fb db
a portion of data of another user, if that
other user has explicitly decided to share it with them e.g.
a game they made that they want another user to have a copy
of
Right now, users log in with
FirebaseSimpleLogin. This is problematic because any malicious user
can create their own account legitimately, and use their account’s
e-mail and password to login with a malicious script, and access the
db
Solutions we’ve considered
1. Store a user_secret to ensure user has legitimate access
Inspired by 2nd method in answer to How to setup Firebase security rules to accept connections only from an iOS App
The structure would look like security->user_secret->associated_user OR security->user->{all_valid_user_secrets}
Security rule: ".read": "root.child('user_secrets/'+auth.uid).exists()"
We could store multiple user secret keys per user, allowing access from multiple verified sources (iOS app, web-app, etc)
Problems with #1
How do we restrict write/read access to the security child?
SimpleLogin doesn’t exist for servers
We don’t want this information visible, as a malicious user could technically read it to find info about his/her own account, and then use that to peruse the rest of the db
Same problem as in the problem statement: a user can generate an account legitimately, and then use those credentials to gain malicious access to the db
2. Temporarily Store User Secret
User initiates login
Node server generates password, stores it in restricted security child in Firebase (the server would be able to do this, as Firebase Secret allows full access)
We authorize firebase client side using Firebase SimpleLogin as we have been
The user interacts with the app. Firebase security rules only allow read/write access if the security child written by the node server is present
User initiates logout/crashes/closes app
Node server removes password from restricted security child
Unauthorize Firebase ref as we have been
Done
Problem with #2 - The issue with this method is the user is vulnerable while they’re logged in, as their security information would be present.
3. Use built in Firebase Security Rules
We hoped there was a built in firebase solution, but haven’t found one that solves the resolves the above problem. If you can point us to one that would be terrific.
We are hoping someone can help shed light on the best approach here, either using our ideas or another route. Thanks so much for your help.
You've essentially asked for someone to write an entire security schema for your app. It would be better if you understood security rules thoroughly before attempting to apply them to a complex structure like this. A good study of the docs from start to end would take you a long way to a fully functional solution.
Let's just focus on what seems to be the core problem, which is that you aren't sure how to make invites work securely with a client-only solution. (The node.js solution should also be obvious with this understanding, given the additional firepower provided by being able to create our own tokens) I'll make a lot of assumptions here; just apply these ideas to your current use case as you see fit.
A data structure:
/invites/$game_id/$uuid/true (a place to store invited users)
/accepted_invites/$gameid/$userid/$uuid/true (a place to store accepted invites)
/games/$game_id (the place we want to invite users into)
/users/$user_id (a place where we put profiles for existing users)
1) When a new user creates an account in the app, write their profile into /users. Secure users/ as follows:
"users": {
"$userid": {
".write": "auth.uid === $userid"
}
}
2) To invite a user, create a uuid, which represents an unguessable id, and store it in invites/$game_id. Note that nobody should be able to read this path.
"invites": {
"$game_id": {
"$invite_id": {
// I can only create an invite for groups I'm a member of
".write": "root.child('games/'+$game_id+'/members/'+auth.uid).exists()",
".validate": "newData.val() === true"
}
}
}
3) To join a game, a user must first accept the access token, which proves that they
know the token (since they can't read the invites path) and links the token to their
account id. The value of this entry is the uuid of the invite.
"accepted_invites": {
"$game_id": {
"$user_id": {
".write": "auth.uid === $user_id",
".validate": "root.child('invites/'+$game_id+'/'+newData.val()).exists()"
}
}
}
4) A user can write themselves into a game if they have accepted an invite or when it is initially created and there are no members yet (the !data.parent().exists() rule)
"game": {
"$gid": {
"members": {
"$uid": {
".write": "auth.uid === $uid",
// I can join a group if a) I'm creating it or b) I have accepted an invite
".validate": "!data.parent().exists() || root.child('accepted_invites/'+$gid+'/'+auth.uid).exists()"
}
}
}
}
Another enhancement to our client-side solution would be to assign the invites a priority, which represents when they expire, and then reference that priority in the security rules to control how long the token is valid.
I ended up going with idea #3, thanks to the help of #Kato's suggestions. The solution of using the built in rules and designing a schema allowed us to avoid needing a 3rd party auth server, so far. Some example of the schema was as follows:
"game_detail" : {
"$game_detail" : {
".read" : "data.child('owner').val() === auth.email || root.child('admins').val() === auth.email",
".write" : "newData.child('owner').val() === auth.email || root.child('admins').val() === auth.email"
}
},
Then, the additional key that made #3 possible was that, in addition to having a schema of security rules, we also created a generic admin credential that can be used by anonymous users when they are logged in to access a subset of the DB and perform necessary cross account operations.
Thanks all for the input here.