I would like to know if it is possible to implement Firebase security in such a way as to allow a each user of my app full access to data in their own location, while enabling the user them self's to enable or disable the type of access from other users to their own data? Or basically, is it possible to implement simple sharing, Dropbox or Google Drive style, among the users of my app, in such a way that it is strictly enforced?
Since Firebase security rules allow you to reference data in Firebase, you can base the security rules on anything you can create data for. So yes, you could allow a user to share their own data in just about any way you could scheme up.
To contrive a simplified example based on the dropbox idea, I could have a "shares" folder under my data, and a security folder where I store access rights:
/security/$user_id/$friend/... // where I put the access rights
/folders/$user_id/shares/... // where I put the shared files
Now I could control access to it by putting user names and a list of folders they can access into my Firebase data:
/security/$user_id/$friend_id = /never/gonna/give/you/up = true
Now in my security rules, I can write something like this:
{
"security": {
"$user_id": { // only authenticated user may read/write his rules
"shares": {
".read": "auth.id === $user_id",
".write": "auth.id === $user_id"
}
}
}
"folders": {
"$user_id": {
// only authenticated user may read/write his folders
".read": "auth.id === $user_id",
".write": "auth.id === $user_id",
"shares": {
// but my friends can read data in shares
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares').val() === true"
}
}
}
}
Note that one limitation on this (for the time being) is that security rules cannot work recursively or in any nested manner. However, since the rules are permissive (if any parent of the path allows access, then it is allowed), you can work around this.
You could need to place a hard limit on the max number of child paths and manually declare them in the rules like so:
// allow sharing up to 3 levels deep
"shares": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares').val() === true",
"$child1": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1).val() === true",
"$child2": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1+'/'+$child2).val() === true",
"$child3": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1+'/'+$child2+'/'+$child3).val() === true",
}
}
}
}
Not the prettiest thing to look at, but a good temporary solution until Firebase gets some nesting features.
Related
I have a Firebase database that I want to only allow users who have access to that application to be able to read from and write to.
My data structure is like so:
{
"applications": {
"id_1": {
"feature": {
"a": true,
"b": false
},
"users": {
"user_id_1": true,
"user_id_2": true
}
}
}
}
As you can see, each application can have many users who have read/write access.
I only want users in the users object to be able to retrieve that application.
I have rules like so:
{
"rules": {
"applications": {
".read": "auth != null",
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
".read": "auth != null", allows any user who is logged in to be able to retrieve all applications. I only want users user_id_1 or user_id_2 to be able to read that application.
In pseudo code, I would do something like this:
{
"rules": {
"applications": {
".read": "only users in `root.applications.$appId.users` can read", // I need to replace `$appId` some how
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
How can I restrict it so when user user_id_1 fetches their applications, they only see apps they have access to?
You're hitting a few common problems here, so let's go through them one by one.
1. Security rules can't filter data
You're trying to control in your rule on /applications what application will be returned when a user tries to read that node. Unfortunately that is not possible, because security rules grant all-or-nothing access.
So either the user has access to /applications (and all data under it), or they don't have access to it. You can't set a rule on /applications to grant them access to some child nodes.
In the documentation, this is referred to as rules are not filters and the fact that permission cascades.
2. Avoid nesting data
You're trying to grant access to /applications, but then store two types of data under there for each application. In cases like that, it is usually better to store each type of data as its own top-level list.
So in your case, that'd be:
{
"application_features": {
"id_1": {
"a": true,
"b": false
},
},
"application_users": {
"id_1": {
"user_id_1": true,
"user_id_2": true
}
}
}
This allows you to grant separate access permissions for the application users and its features. While it means you'll have to read from both branches to get all information of each user, the performance difference there is negligible as Firebase pipelines those requests over a single socket
For controlling access and the most scalable data structure, Firebase recommends that you avoid nesting data and flatten your data structure.
3. Model the data in your database to reflect the screens of your app
Since granting anyone access on /applications gives them access to all data under that, you'll likely need another place to store the list of applications for each user.
I usually make this list explicit in my databases, as another top-level list:
{
...
"user_applications": {
"user_id_1": {
"id_1": true
},
"user_id_2": {
"id_1": true
}
}
}
So now when you want to show the list of applications for the current user, you load the IDs from /user_applications/$uid and then look up the additional information for each app with extra calls (which in turn can be pipelined again).
This one is not in the documentation, but a common pattern with NoSQL databases. I recommend checking out my answers to Many to Many relationship in Firebase and Firebase query if child of child contains a value.
I recently received an email from firebase telling me that my realtime database has insecure rules. These are the rules that I have set:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Is this not a secure rule?
Email/Password is the only sign-in method that I have enabled.
firebaser here
I'm sorry if the email wasn't very explicit about what isn't secure about those rules. Securing your user's data is a crucial step for any app that you make available, so I'll try to explain a bit more about how that works below.
The (default) rules you have allow anyone who is signed in to your back-end full read/write access to the entire database. This is only a very basic layer of security.
On the one hand this is more secure than just granting everyone access to your database, at least they have to be signed in.
On the other hand, if you enable any auth provider in Firebase Authentication, anyone can sign in to your back-end, even without using your app. Depending on the provider, this can be as easy as running a bit of JavaScript in your browser's developer console. And once they are signed in, they can read and write anything in your database. This means they can delete all data with a simple command like firebase.database().ref().delete().
To make the data access more secure, you'll want to more tightly control what each signed-in user can do. For example, say that you keep a profile with information about each user under /users. You might want to allow all users to access these profiles, but you definitely want users to only be allowed to modify their own data. You can secure this with these rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
With these rules, everyone (even non-authenticated users) can read all profiles. But each profile can only be modified by the user whose profile it is. For more on this, see the Firebase documentation on securing user data.
In addition to ensuring that all access to data is authorized, you'll also want to ensure that all data stored is valid to whatever rules you have for you app. For example, say that you want to store two properties for a user: their name, and their age (just for the sake of the example, in reality you'd probably store their date-of-birth instead). So you could store this as something like:
"users": {
"uidOfPuf": {
"name": "Frank van Puffelen",
"age": 48
}
}
To ensure only this data can be written, you can use this rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid",
".validate": "data.hasChildren('name', 'age')",
"name": {
".validate": "data.isString()",
},
"age: {
".validate": "data.isNumber()",
},
"$other: {
".validate": false
}
}
}
}
}
These rules ensure that each user profile has a name and age property with a string and numeric value respectively. If someone tries to write any additional properties, the write is rejected.
Above is a quick primer on how to think about securing your (user's) data. I recommend that you check out the Firebase security documentation (and the embedded video) for more.
Update: since May 2021 you can also use Firebase App Check to restrict access to calls just coming from your web site or app. This is another, quick way to reduce the abuse of your database. This approach is not foolproof though, so you'll want to combine App Check for broad protected, with the security rules for fine-grained control.
You can also mute alerts by visiting the link at the bottom of the email.
https://console.firebase.google.com/subscriptions/project/<YOUR_PROJECT_NAME>
I changed rules
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
But after that page not working.
The user needs to be able to load a list of the other users, which is why I have set the "read" to true. They should only be able to write within their own folder.
My question is, does this expose all of the users information to eachother? If a user has sensitive in one of their folders information, can this be stolen?
{
"rules": {
"users": {
".read": true,
"$userId": {
".write": "$userId === auth.uid"
}
}
}
}
As you said, the ".read": true will give every user access to all the users data, since this rule is saying that we can read everything from the users node in your database.
Your rule only allows the current logged in user to write on its own node, but others users can't.
I am having trouble in writing firebase permissions. I want those users if authenticated only write to users section and Everyone else should be able to read or write to any section of the database. Is there any way that I can define rules for every table default to true and restrict only user section to be authenticated or I have to explicitly write rules for every table.
PS. It would be great if someone could guide me what rules should I implement for an app with features for sending and receiving a message with the following structure:
-Chat
-Friends
-Users
-message_notifications
-messages
-notifications
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
"Users":{
"$uid":{
".read": true,
".write": "auth.uid == $uid"
}
}
}
}
Is there any way that I can define rules for every table default to true and restrict only user section to be authenticated?
Once a user has access to data at a certain level in your database, they have access to all data under that level. You cannot revoke this permission on a lower level. So there's no way to give a user access to all data at the root, and then exclude one node.
What you can do is use the $ wildcard rules to create two types of top-level nodes:
{
"rules": {
"Users":{
"$uid":{
".read": true,
".write": "auth.uid == $uid"
}
},
"$others": {
".read": true,
".write": true
}
}
}
With the above rules, users can:
Only read the /Users/$uid node of a user if they know the UID of that user.
Can only write their own /Users/$uid node.
Can read and write all other data.
I recently received an email from firebase telling me that my realtime database has insecure rules. These are the rules that I have set:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Is this not a secure rule?
Email/Password is the only sign-in method that I have enabled.
firebaser here
I'm sorry if the email wasn't very explicit about what isn't secure about those rules. Securing your user's data is a crucial step for any app that you make available, so I'll try to explain a bit more about how that works below.
The (default) rules you have allow anyone who is signed in to your back-end full read/write access to the entire database. This is only a very basic layer of security.
On the one hand this is more secure than just granting everyone access to your database, at least they have to be signed in.
On the other hand, if you enable any auth provider in Firebase Authentication, anyone can sign in to your back-end, even without using your app. Depending on the provider, this can be as easy as running a bit of JavaScript in your browser's developer console. And once they are signed in, they can read and write anything in your database. This means they can delete all data with a simple command like firebase.database().ref().delete().
To make the data access more secure, you'll want to more tightly control what each signed-in user can do. For example, say that you keep a profile with information about each user under /users. You might want to allow all users to access these profiles, but you definitely want users to only be allowed to modify their own data. You can secure this with these rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
With these rules, everyone (even non-authenticated users) can read all profiles. But each profile can only be modified by the user whose profile it is. For more on this, see the Firebase documentation on securing user data.
In addition to ensuring that all access to data is authorized, you'll also want to ensure that all data stored is valid to whatever rules you have for you app. For example, say that you want to store two properties for a user: their name, and their age (just for the sake of the example, in reality you'd probably store their date-of-birth instead). So you could store this as something like:
"users": {
"uidOfPuf": {
"name": "Frank van Puffelen",
"age": 48
}
}
To ensure only this data can be written, you can use this rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid",
".validate": "data.hasChildren('name', 'age')",
"name": {
".validate": "data.isString()",
},
"age: {
".validate": "data.isNumber()",
},
"$other: {
".validate": false
}
}
}
}
}
These rules ensure that each user profile has a name and age property with a string and numeric value respectively. If someone tries to write any additional properties, the write is rejected.
Above is a quick primer on how to think about securing your (user's) data. I recommend that you check out the Firebase security documentation (and the embedded video) for more.
Update: since May 2021 you can also use Firebase App Check to restrict access to calls just coming from your web site or app. This is another, quick way to reduce the abuse of your database. This approach is not foolproof though, so you'll want to combine App Check for broad protected, with the security rules for fine-grained control.
You can also mute alerts by visiting the link at the bottom of the email.
https://console.firebase.google.com/subscriptions/project/<YOUR_PROJECT_NAME>
I changed rules
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
But after that page not working.