I am trying to block hackers from modifying or seeing the data on my Firebase database. Users in my database contains the user IDs of all the users in Authentication tab. Can I make the database to only let see data by the user logged in?
This is my database structure:
firebase database structure
I want to allow users to modify their own user ID's entry in "users" and I want to allow only the admin to control "orders". Im using Firebase in a Electron app so client is connecting to firebase in Node.js
Yes, this is definitely doable using Firebase Database Rules
What you've described seems like it would have three parts, but they we they combine also affects how you need to write them.
I'll start by going over the 3 pieces separately and then will move into combining them.
Read/Write By Only Logged In Users
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
By placing this at the top level of your rules, you will prevent any reading or writing by anyone not logged in through one of the supported Firebase authentication methods.
Users Modifying User Entry
{
"rules": {
"users": {
"$uid": {
".write": "auth.uid === $uid"
}
}
}
}
This specifies that under the user path, for each user id (using the $uid syntax to specify it as a variable to be used later), only allow writing when the current authenticated user id matches that same id.
Admin Control Of Orders
{
"rules": {
"orders": {
".read": "auth != null && auth.token.isAdmin",
".write": "auth != null && auth.token.isAdmin"
}
}
}
We take a similar approach for orders as we did for users entries, but instead of checking the user id we simply say that only an admin can do reads and writes anywhere under orders.
Putting It All Together
Now, if these rules came together in a way such that more specific rules overrode less specific rules (like CSS selectors, for instance), then we could simply merge all of the above together like this:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"orders": {
".read": "auth != null && auth.token.isAdmin",
".write": "auth != null && auth.token.isAdmin"
},
"users": {
"$uid": {
".write": "auth.uid === $uid"
}
}
}
}
However, Firebase database rules are somewhat of an inverse of this. Providing access in shallower rules will override any deeper rules attempting to deny access (although it is possible to grant more privileges in deeper rules). So the above will actually give full read/write access to the entire DB by any logged in user.
In light of that, we will need to be more careful when combining these, to ensure that our separate intents stay valid. Most importantly, the first section Read/Write By Only Logged In Users will be the section that requires modification, since it was at the top level.
Luckily, our rules from parts 2 & 3 take care of most of what we were getting from part 1 anyways. This only holds true if there are only orders and users in your db. The only thing we are missing is a read rule on users. It's not entirely clear what sort of rules you want for reading user data, since you only specify that a user can only write his or her own data, so I will assume that all logged in users can read user data.
The merge then becomes:
{
"rules": {
"orders": {
".read": "auth != null && auth.token.isAdmin",
".write": "auth != null && auth.token.isAdmin"
},
"users": {
".read": "auth != null",
"$uid": {
".write": "auth.uid === $uid"
}
}
}
}
Please note that you will need to duplicate the auth != null rule for read and/or write in other nodes of your db if you have them, since here you only show off orders and users.
Hope that helps!
Related
I am struggling with the security rules on the firebase realtime database. My database structure is as following:
On the first level you have Chats. On the second level there are the Chat partners (a concatenated string with user IDs of chat partners). On the third level you have the messages. And on the last level there are the variables datetime, userid and message.
My question is, how can I get into the second child node, e.g. check if the auth.uid is within the concataneted string. My idea was to give the users read access, if the userid is within the chatpartners string, to ensure that only the chatpartners can read their messages. Or is this a thinking error?
I have tryid a lot of things, however no success:
"Chats":{
".read": "root.child('Chats').val().contains('auth.uid')"
}
#Update
{
"rules": {
"Chats": {
"$chatid": {
".read": "$chatid.contains(auth.uid)",
".write": "auth != null"
}
},
"Suchencards": {
".read": "true",
".write": "auth != null"
},
"UserData": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
I am using the playground on firebase console to check if I get true or false, however in that case I get false.
#Update 2
Screen1
Screen2
You can only access data in your rules if you know the exact path to that data, so you won't be able to access all chats from a rule on /Chats. Trying to do so typically means that you're trying to define your rules on the wrong level.
For example, if you want to allow a user to read a specific child of Chats, you'll need to define rules on that level:
"Chats":{
"$chatid": {
".read": "$chatid.contains(auth.uid)"
}
}
While the above will work for accessing a specific room, you will not be able to use this to query for all chat rooms that a user has access to (as Firebase queries don't support a "contains" operation). Have a look at the userChatrooms node in my answer here for a better model for tracking the chat rooms for a user: Best way to manage Chat channels in Firebase
I have been getting email notifications about my Firebase rules not being secure even though I set the data to be readable only by authenticated users and I set the write to be false. My application basically shows currency exchange rates and I create an anonymous user once the app loads for the user to be able to read the data so I think it makes sense to have such rules? Here are my rules:
{
"rules": {
".read": "auth.uid != null",
".write": false
}
}
Update: I updated my rules to:
{
"rules": {
"currencies":{
".read": "auth.uid != null",
},
".write": false
}
}
And here is my data:
(https://i.stack.imgur.com/in1BV.png)
I set the data to be readable only by authenticated users
While that is a good starting point, it means I can get all your (user's) data by signing in and reading the root. This is not secure and open to abuse, which is why you receive alerts about it.
You should follow the principle of least privilege, and only allow in your rules exactly what your code does.
In the unlikely event that your code really reads the root, you can disable the alert in the Firebase console.
In all other cases, you'll want to reduce the read permission to exactly how your code reads it. For example, if your code reads from two top-level nodes, you can secure that with:
{
"rules": {
"users": {
".read": "auth.uid != null",
},
"messages": {
".read": "auth.uid != null",
},
".write": false
}
}
In a scenario like this, you're likely to read the /messages first, and then read the profile information for specific users mentioned in those messages. You can better secure that like this:
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid != null",
}
},
"messages": {
".read": "auth.uid != null",
},
".write": false
}
}
Now a malicious user can't read all of /users in one go, but can read the profile only once they know the UID. A sufficiently motivated user can still read your entire database, but it'll take a lot more effort - which discourages abuse.
Also think of whether the user needs to read all message, or (say) maybe just the 50 most recent ones. The latter use-case you can secure better with query based security rules:
"messages": {
".read": "auth.uid != null &&
query.orderByChild == 'timestamp' && &&
query.limitToLast <= 50"
},
Now the user can only read the 50 most recent messages, and thus (with the previous) also only the user profiles that participated in those messages.
Finishing off with the golden rule: only allow in your rules exactly what your code does. Make sure your code works, but any different API calls get rejected.
Update:
This is how the structure of my firebase db looks like it has two main nodes results and surveys as shown"
These are my current rules:
{
"rules": {
"surveys": {
".indexOn":["userId"],
".read": true,
".write": "auth.uid != null"
},
"results": {
".read": "auth.uid != null",
"$resultid": {
".write": true
}
}
}
}
Basically when a user makes a new survey it gets stored it saves the template in surveys, right now only authenticated users are able to write to it so its perfectly fine, now comes the reading part, right now anyone is able to read the entire surveys node, I cannot add auth != null to it as when Someone shares their survey even unauthenticated users should be able to read that particular survey and do it, so what should I do so as to not allow everyone to read the entire surveys node but only the one they should be reading, orelse anyone would be able to perform anyone's survey by pulling up all the ids. By the way the $resultid rule doesnt work and still allows write access right now :(
The closest you can get it:
{
"rules": {
"surveys": {
".read": "auth != null",
"$surveyid": {
".read": true,
".write": true
}
}
}
}
These rules:
Allow any authentication user to read all surveys.
Allow anyone who knows a survey ID to read/write that survey. You'll note that this is slightly different than your second requirement, as there is no way to know who created each survey unless they are signed in.
Update
For the results you'd then add:
"results": {
".read": "auth != null",
"$resultid": {
".write": true
}
}
I would like to create a newsletter subscription form. Meaning a user can register to my newsletter without creating an account.
I would like to do a simple insertion. But what is the best way to secure it ?
I have implemented "Anonymous Log In" (https://firebase.google.com/docs/auth/web/anonymous-auth)
I need basic rules, read to check is the user is allready registered and write to push user data into Realtime DB.
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
when the Anonymous Login is done, I push into the Realtime Database.
let dbConnection = firebase.database().ref('/newsletter_user');
dbConnection.push(datas).then(callback);
The problem is that Anonymous Auth do not check Auth Domain. (How to block localhost in firebase?)
Meaning a user can grab my ApiKey and perform insert or read data into my realbase database.
Then it do not seems really secure to me. What can I do to improve security ?
One thing you can do to improve your security is limiting your database access. Currently you have basic read/write rules for your database wich means that someone that is authenticated can do anything in your database. If I want I could write an entire encyclopedia in your datadump node :)
So the first thing you can do is limit read/write access to specific locations:
{
"rules": {
"newsletter_user" : {
".read": "auth != null",
".write": "auth != null"
}
}
}
Now I can only do something under the newsletter_user node.
Next you can use validation rules to make sure only the data you want can be written:
{
"rules": {
"newsletter_user" : {
".read": "auth != null",
".write": "auth != null",
// a valid newsletter_user must have attributes "color" and "size"
// allows deleting newsletter_user (since .validate is not applied to delete rules)
".validate": "newData.hasChildren(['color', 'size'])",
"size": {
// the value of "size" must be a number between 0 and 99
".validate": "newData.isNumber() &&
newData.val() >= 0 &&
newData.val() <= 99"
},
"color": {
// the value of "color" must exist as a key in our mythical
// /valid_colors/ index
".validate": "root.child('valid_colors/' + newData.val()).exists()"
}
}
}
}
So I'm delving into Firebase security rules and as far as I understand, rules that are specified higher up in the tree cascade further down into the tree.
So I'm wondering if there's a way to make a case work where I basically have a /bands subtree that I want writeable by anyone, however there are admins and members subtrees where I want only writeable, based on special conditions.
So far, this is kinda what I have going on:
{
"rules": {
".read": "auth != null",
"bands": {
"$bandId": {
".write": "auth !== null",
"$bandId": {
".write": "auth !== null && data.child('creator_id').val() === auth.uid"
}
}
}
}
}
When I go to test "writing", in the Firebase simulator, something like /bands/-KnLeIHM4zCspwBZjZP9 where the creator_id does NOT match the specified auth.uid I have provided, I still get a simulator write success, due to the /bands tree-level having the write access.
Is there any clever way to do allow anyone to "push" to /bands but then when it gets down to the actual /bands/$bandId level, it starts looking at these various conditions? Or am I going to have to rework my data and separate out the trees into even more trees? I have other instances where this kind of thing is necessary, but this is the most succinct version I am working with that I need to solve.
Any thoughts? Thanks in advance :)
{
"rules": {
"bands": {
".read": "auth != null",
".write": "!data.exists() && auth != null",
"$bandId": {
".write": "data.child('creator_id').val() === auth.uid"
}
}
}
}
".write": "!data.exists() && auth != null" will only allow authenticated users to write to paths within bands if they don't exist (creating new content).