I'm having issues reading and writing data from my database on the client (my iOS app) using the following database rules:
// Checks auth uid equals database node uid
// In other words, the User can only access their own data
{
"rules": {
"posts": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
However, I have no issues reading and writing my data when using the following rule:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
My goal is to have each user only have the ability to read/write their own data. Any suggestions/help would be appreciated.
EDIT:
When attempting to post I use the following (iOS):
let key = ref?.childByAutoId().key
let post = ["uid": key,
"title": titleField.text,
"description": descField.text]
ref?.child(key!).setValue(post)
When I want to retrieve those data entries, currently I am attempting to retrieve the data entries by looking at the reference point ("task") (in iOS, my database reference is the following):
ref = Database.database().reference().child("task")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject]
else {
return
There are a couple things going on here.
First as Doug answered, your rules have to match your query.
Second, when using childByAutoId() you are creating a random key. Instead you should be using the UID of the logged in user.
And third when reading from the database you have to make sure to read from the location where you have placed your rules. Currently you are trying to read the entire list were there are no rules defined. (When no rules are defined Firebase defaults to false) Instead you should read the child of the list.
You can also take a look at my answer here for some more explenation and links to relevant docs.
Your rules don't match your queries. Your query is accessing documents at a node called "task", but your rules are protecting a node called "posts". You should edit your rules to match the queries you intend to protect.
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.
I am trying to add a simple rule to firebase database. the child node activities have proper access but the leads don't. if I sent the parent read true and then child rules are ignored. how can I set parent true to public but child restricted?
{
"rules": {
"leads": {
"$activity": {
".read":
"root.child('users_business_activities').
child(auth.uid).hasChild(data.child('category').val())",
".write" :
"root.child('users').child(auth.uid).child('isAdmin').val() == true"
}
},
"users": {
"$uid": {
".read": "auth.uid == true",
".write": "auth.uid == $uid"
}
},
"business_activities": {
".read": "auth.uid == true",
".write": "false"
}
}
}
In the Firebase Realtime Database permissions cascade downwards. Once you give a user read permission on /leads, you can't take that permission away lower in the JSON tree.
This has a few consequences:
Security rules cannot be used to filter data. This is known as rules are not filters in the Firebase documentation. I also recommend reading some of the many existing questions on this topic.
You will often need to create a lookup list of the activity IDs that a user has access to. Such a lookup list is often known as an "index" in Firebase terms. The documentation on creating scalable data structures has a good example of such a structure.
If part of the data for an activity/lead needs to be publicly readable, and part needs to remain private, you'll want to split the public and private parts into separate top-level nodes. This splitting is known as flattening you data structure in the docs. I also gave an answer here to show how to use this for user profile information.
I am learning how firebase database filters work, and i haven't get a clue on other answers as to how this really works, i have also been reading the docs to see if i could understand it's whole functionality but yet i cannot get my goal (probably i am reading the wrong section)
I have a simple document to insert in the database as follows:
{
"name": "new project"
}
right now i want to apply rules for name to validate if it is a number just for testing (upon figuring this out, i will set more complex conditions). So i have made a simple database rule structure for this scenario:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"projects": {
"$project": {
"name": {
".validate": "newData.isNumber()" // also tried newData.child('name').isNumber()
}
}
}
}
}
if i understood this right $project is the id of the new document to insert and will have the values passed on the inserting document.
I have been using the rule simulator to check on my progress of the rules but everytime i try this with a different code it always go on Simulated write allowed as long as i am authenticated.
So again, how can i apply the rules for the newData.prop in order to set the database a secure schema?
I am building a simple Firebase application with AngularJS. This app authenticates users through Google. Each user has a list of books. Anyone can see books, even if they are not authenticated. Only the creator of a book can edit it. However, individual users need to be able to record that they've read a book even if someone else added it.
I have rules.json like so:
{
"rules": {
".read": false,
".write": false,
"book": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
}
".read": true,
}
}
}
And I am trying to write a book simply with:
$firebaseArray(new Firebase(URL + "/book")).$add({foo: "bar"})
I get a "permission denied" error when trying to do this although I do seem to be able to read books I create manually in Forge.
I also think that the best way to store readers would be to make it a property of the book (a set of $uid for logged-in readers). ".write" seems like it would block this, so how would I do that?
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"readers": {
".write": "auth !== null"
}
},
It seems like a validation rule would be appropriate here as well ... something like newData.val() == auth.uid, but I'm not sure how to validate that readers is supposed to be an array (or specifically a set) of these values.
Let's start with a sample JSON snippet:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"title": "Structuring Data",
"url": "https://www.firebase.com/docs/web/guide/structuring-data.html",
"creator": "twiter:4916627"
},
"-JRHTHaKuITFIhnj02kE": {
"title": "Securing Your Data",
"url": "https://www.firebase.com/docs/security/guide/securing-data.html",
"creator": "twiter:209103"
}
}
So this is a list with two links to articles. Each link was added by a different user, who is identified by creator. The value of creator is a uid, which is a value that Firebase Authentication provides and that is available in your security rules under auth.uid.
I'll split your rule into two parts here:
{
"rules": {
".read": false,
"book": {
".read": true,
}
}
}
As far as I see your .read rule is correct, since your ref is to the /book node.
$firebaseArray(new Firebase(URL + "/book"))
Note that the ref below would not work, since you don't have read-access to the top-level node.
$firebaseArray(new Firebase(URL))
Now for the .write rules. First off is that you'll need to grant users write-access on the book level already. Calling $add means that you're adding a node under that level, so write-access is required.
{
"rules": {
"book": {
".write": "auth != null"
}
}
}
I leave the .read rules out here for clarity.
This allows any authenticated user to write to the book node. This means that they can add new books (which you want) and change existing books (which you don't want).
Your last requirement is most tricky. Any user can add a book. But once someone added a book, only that person can modify it. In Firebase's Security Rules, you'd model that like:
{
"rules": {
"book": {
".write": "auth != null",
"$bookid": {
".write": "!data.exists() || auth.uid == data.child('creator').val()"
}
}
}
}
In this last rule, we allow writing of a specific book if either there is no current data in this location (i.e. it's a new book) or if the data was created by the current user.
In the above example $bookid is just a variable name. The important thing is that the rule under it is applied to every book. If needed we could use $bookid in our rules and it would hold -JRHTHaIs-jNPLXOQivY or -JRHTHaKuITFIhnj02kE respectively. But in this case, that is not needed.
First off the "permission denied" error. You are getting this error because you are trying to write directly in the "book" node instead of "book/$uid".
Example of what you do now:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"foo": "bar"
},
"-JRHTHaKuITFIhnj02kE": {
"foo": "bar"
}
}
In your rules you have a global rule for write set to false so that will be the default and next to that you have made a rule for the specific node book/$uid. So when trying to write directly in "book" it will take the default rule that was set to false. Have a look at Securing your data for more information about firebase rules.
And for the last part of your question i suggest you take a look at Structuring data for more information about the best ways to structure your data inside firebase.
So taka a good look at what and how you want to save and write in firebase and make sure your rules are structured accordingly.