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.
Related
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!
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()"
}
}
}
}
I hae a project database structure :
users
isSuperMan: false
isAdmin: false
info:
givenName: xxxxxx
address: yyyyyy
....
memberships:
2018:
paid: 10
paidOn: 20198-01-01
paymentType: 1
2017:
paid: 10
paidOn: 20198-01-01
paymentType: 1
....
and my current Firebase rules are set to allow a logged user to create/update/delete his own data
{
"rules": {
".read": "auth.uid != null",
".write": false,
"users": {
"$uid": {
".write": "$uid == auth.uid"
}
}
}
}
As I am totally newbie with Firebase rules , I don't see how to set them to handle the following business cases , any help appreciated
I wonder if I should not go for Firestore to handle such cases ?
logged user with Superadmin or Admin role ( true) can read/write ALL DATA
standard logged user can read/write his own info
standard user can read but cannot update/delete memberships
memberships data are written only when logged user is paying it
To give someone with the admin role read access to the whole database, use these rules:
{
"rules": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true"
}
}
This read rule replaces what you currently have, since right now all signed in users can read the entire database.
To then allow each user to read/write their own info, modify the above to:
{
"rules": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true",
"users": {
"$uid": {
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
}
}
I highly recommend checking out the Firebase documentation on security rules, specifically the section on securing user data (which covers step two I showed above), as well as the great video explaining security rules.
My app allows anyone to write a new message (the message cannot be updated or deleted), which should then only be visible to authenticated users. The authenticated users can edit the data (which will include things like flag as important, etc.). Think of the app like a private suggestion box (anyone can submit a suggestion but only the admins can view the submitted suggestions). I'm using the Firebase simulator and the following fails but it shouldn't:
Firebase Simulator
Write
Location: messages/
Authenticated: false
Data (JSON)
{
"key": "value"
}
Firebase Database Rules
{
"rules": {
"messages": {
"$message": {
".read": "auth !== null",
".write": "!data.exists() || auth !== null",
},
},
"users": {
".read": "auth !== null",
".write": "auth !== null"
}
}
}
I think that's because you're testing with messages/ whereas only writes to messages/{message-id} would be allowed. Try writing to messages/somethingrandom.