I am having some issues with Firebase security rules. I want any user to be able to create an account using my iOS app at the same time once he or she establishes the account I want to have two nodes one is private, and one is public. I want the public to be accessed by anyone, including the user its self, but the user that created the account only accesses the private node. I have tried a lot of things, but none of my work seems to work. I want to fetch all the public node values by just having a link without knowing the uid of each user
1- Anyone can create an account
2- Only the user can access his own private node
3- A link where I can fetch all of the user's public node only
Thank you!!!!
for example, I would like to fetch all the users https://id.firebaseio.com/Ireland.json
Here is some of my work
{
"rules": {
"Ireland": {
"users": {
"$uid": {
"private": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
},
"public": {
".read": true,
".write": "auth != null && auth.uid == $uid",
},
},
},
},
},
}
here is my swift code, but every time I try to create an account it says permission denied
guard let uid = result?.user.uid else { return }
let privateValues = ["email": email, "username": username, "password": password, "profileImageUrl": profileImageUrl, "country": "Ireland"]
let publicValues = ["username": username, "profileImageUrl": profileImageUrl]
let values = [uid: ["private": privateValues, "public": publicValues]]
Database.database().reference().child("Ireland").child("users").updateChildValues(values, withCompletionBlock: { (error, reference) in
if let error = error {
print("Failed to save user info to database: ", error)
self.signUpButton.wiggle()
return
}
Firebase security rules cannot be used to filter data. All they do is check if a certain read operation is allow, without checking each individual node.
So if you attach a listener to /Ireland, the server checks if the current user has read permission to /Ireland. Since nobody has permission on that level, the read operation is rejected.
This is also known as 'rules are not filters' in both the documentation and previous questions.
Last year Firebase added support for validating queries in security rules, so that for example you can allow queries that also filter by a ownerUID type property. See the documentation on query based rules for more on that.
But that won't work for your use-case either, since read operations always return full nodes. Which brings us back to the fact that security rules can't be used to filter data.
You will have to separate the public and private data into two separate top-level nodes: private and public. This is one of the many reasons the Firebase documentation recommends keeping your data structure flat.
Also see:
How to create public/private user profile with Firebase security rules?
Firebase: How to structure public/private user data
Related
as soon as I use a wildcard I get this error:
Unhandled Exception: [firebase_database/permission-denied] Client doesn't have permission to access the desired data
works:
{
"rules": {
"user": {
".write": "auth != null",
".read": "auth != null"
}
}
doesn't work:
{
"rules": {
"user": {
"$userId": {
".write": "auth != null && $userId === auth.uid",
".read": "auth != null"
}
}
}
doesn't work:
{
"rules": {
"user": {
"$userId": {
".write": true,
".read": true
}
}
}
db structure:
code:
List<User>? userlist;
late Query query;
void initState() {
userAuth.FirebaseAuth.instance.authStateChanges().listen((userAuth.User? user) {
final FirebaseApp abcApp = Firebase.app();
final FirebaseDatabase database = FirebaseDatabase.instanceFor(app: abcApp);
userlist = [];
query = database
.ref().child("user").orderByChild("userId")
.equalTo(
ownUid==true
?user!.uid
:widget.peerId
);
_onOrderAddedSubscription1 = query.onChildAdded.listen(onEntryAdded1);
_onOrderChangedSubscription1 = query.onChildChanged.listen(onEntryChanged1);
});
super.initState();
}
My guess is that you're trying to read from /users. If you do that with the second set of rules, it gets rejected as those rules don't grant anyone permission to read all of /users - but only allows one to read /users/$uid.
It helps to recall that security rules on their own don't filter data, but instead merely check whether all data access is authorized.
So if you want to allow reading from /users, you need a rule on /users that allows that read. And if you want to allow reading specific data under /users, you either need to read from that specific path or combine a query and rules so that the rules can verify that the client is only reading data they're authorized for.
This problem comes up quite regularly, so I also recommend checking out more questions about 'rules are not filters'
In my database, I have a users node that contains data under a user ID.
This includes their bio, number of followers, whether the user is a moderator, and more.
users
{
userId1
{
bio: "Example bio..."
followers: 250
moderator: true
...
}
}
In order for the number of followers to be correct, I use a transaction block to increment the followers property every time the follow button is clicked. There are a few other properties that require transaction blocks as well.
Unfortunately, I have discovered that in order for the transactions to work, the security rules for the $userId node must be set to: “.write”: “auth != null”. Otherwise, the number of followers property won’t be incremented when someone clicks the follow button. Because the transaction block queries the entire user, we can’t limit the security rules to just the “followers” property.
"users":
{
"$userId":
{
// Has to be set like this or transactions won't work
".read": "auth != null",
".write": "auth != null",
"bio":
{
// This will have no effect due to rule cascading
".write": "auth.uid === $userId"
}
"moderator":
{
// This will have no effect due to rule cascading
".write": ...
}
}
}
And since rules cascade, this makes it seem impossible to set specific rules for any other properties under user, including bio and whether the user is a moderator etc. This makes the user property vulerable to changes by malicious users.
The same thing happens for a post and likes, the example used in the Firebase documentation. Because the transaction block queries the entire post, we can’t limit the security rules to just the “likes” property. All of the other post properties will have to settle for the “.write”: “auth !=null” setting because of cascading.
The best I can do is to use validation, but that won’t stop malicious users from setting their follow count to 10,000 or making themselves a moderator if they somehow gain access.
Using Firebase Rules, is there any way to secure nodes that have transactions run on them?
Edit: More Info
This is a simplified version of what my transaction block looks like for incrementing the follower count:
// Run transaction block on the user in the "users" node
userRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
// Store the user
if var user = currentData.value as? [String: AnyObject]
{
// Get the number of followers
var numberOfFollowers = user["numberOfFollowers"] as? Int ?? 0
// Increase the number of followers by 1
numberOfFollowers += 1
// Set the new number of followers
user["numberOfFollowers"] = numberOfFollowers as AnyObject?
// Set the user value and report transaction success
currentData.value = user
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
})
This is how followers are stored in my database:
myDatabase: {
followers: {
"andrew098239101": {
// These are all the user ID's of users that follow "andrew098239101"
"robert12988311": true
"sarah9234298347": true
"alex29101922": true
"greg923749232": true
}
"robert12988311": {
"alex29101922": true
}
}
...
users: {
"andrew098239101": {
// Andrew's user info
"bio": "hello I am Andrew"
"numberOfFollowers": 4
"moderator": true
...
}
"robert12988311": {
"bio": "I'm Robert"
"numberOfFollowers": 1
"moderator": false
...
}
}
}
There is a similar node for following, etc.
You should be able to set the looser write permission on the followers property only. So something like this:
"users": {
"$userId": {
".read": "auth != null",
".write": "auth.uid === $userId",
"followers": {
".write": "auth.uid !== null"
},
}
}
With this, all authenticated users can write to /users/$uid/followers, but only the owner of a profile can write the other values.
I'd recommend separating the followers from the other user profile data, so that you have two top-level lists: users/$uid with the user profile that only the owner can write, and followers/$uid with the data that followers can write.
I also recommend not just storing the count, but also storing the UID of their followers. In fact, I'd use this structure:
users: {
"uidOfMichael": {
followers: {
"uidOfFrank": true,
"uidOfJames": true,
"uidOfAndrew": true
},
followerCount: 3
},
"uidOfFrank": {
followers: {
"uidOfJames": true,
"uidOfAndrew": true
},
followerCount: 2
},
"uidOfJames": {
followers: {
"uidOfAndrew": true
},
followerCount: 1
},
"uidOfAndrew": {
followers: {
"uidOfJames": true
},
followerCount: 1
}
}
Now you can validate these additional things:
A user can only write their own UID to followers.
A user can only increment followerCount when they added their UID to followers
The rules for that would be something like (typos possible):
"users": {
"$userId": {
".read": "auth != null",
".write": "auth.uid === $userId",
"followers": {
"$followerId": {
".write": "auth.uid !== $followerId"
}
},
"followerCount": {
".write": "
newData.val() === data.val() + 1 &&
!data.parent().child('followers').child(auth.uid).exists() &&
newData.parent().child('followers').child(auth.uid).exists()
"
}
}
}
A = Database.database().reference().child("users");
A.observe(DataEventType.value, with: {snapshot in
The rules:
"rules": {
"users" :
".read": "auth.uid != null",
".write": "auth.uid != null" {
"$uid": {
"Garden" :{
".read": "auth.uid != null"
,".write": "$uid == auth.uid"
}
"Hose": .....
"House": ....
So as you'll see the users is not being assigned a rule because it would override rules for Garden, House, Hose etc. Therefore, I want to alter the code that looks for the snapshot in A to go more directly to the childs of users, vs stopping at users and consequently not passing the security test (since users has no rules).
What comes after A.observe:
for users in snapshot.children.allObjects as! [DataSnapshot] {
let usersObject = users.value as? [String: AnyObject]
let usersGarden = usersObject?["Garden"] as? String
let usersHose = usersObject?["Hose"] as? String
let usersHouse = usersObject?["House"] as? String
......
let USA = UserH(Garder: usersGarden, Hose: usersHose...)
self.users.append.
self.table.reloadData()
You'll see that the targets are the childs that come after uid, so a way to target them could allow me to avoid stopping at users in the snapshot A
As is, the code and the rules in the question match, the code reads the users node and the rules allow any authenticated user to read that node iterate over the child data.
I believe your actual question is
How do I get a more granular control of what can be written to a child
node in Firebase using Firebase Rules
suppose we have a structure that matches your rules
users
uid_0
garden: "rose"
hose: "green"
uid_1
garden: "tomato"
hose: "black"
and suppose we only want to allow a user to update their own garden; other users cannot modify that users garden. e.g. uid_0 can modify only the node /users/uid_0/garden. uid_1 can only modify /users/uid_1/garden etc.
Here's a rule that lets all authenticated users read the users node but only the currently authenticated user can write to their own garden node
{
"rules": {
".read": false,
".write": false,
"users" : {
".read": "auth != null",
"$uid": {
"garden": {
".write": "$uid === auth.uid" //only the authenticated user can write
}
}
}
}
}
Note that I set read and write to false at a high level and am granting access at a lower, more granular level.
So in this case, if there was another node at the same level as 'users' nobody could read or write to it.
I am trying to check if a username already exist in the database.
Here is the code I have written for that..
func textFieldDidEndEditing(_ textField: UITextField) {
//check if username is already taken in the database
let username = self.usernameTextField.text!
let reference = Database.database().reference().child("activeUsernames")
let query = reference.queryOrdered(byChild: "username").queryEqual(toValue: username)
let banner = StatusBarNotificationBanner(title: " The username \(username) is not available.", style: .danger)
let successBanner = StatusBarNotificationBanner(title: "The username \(username) is available.", style: .success)
query.observe(.value, with: { (DataSnapshot) in
if DataSnapshot.exists() {
print("Darn username already exists")
banner.show()
} else {
print("Yes I can use this username")
successBanner.show()
}
}, withCancel: nil)
}
So far it works however inconsistently. Sometimes if I enter a username that already exist the print statement "Yes I can use this username" appears when it clearly is already taken, and sometimes it lets me know it is already taken. I notice I get this statement in the console
Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "username" at /activeUsernames to your security rules for better performance
I'm not sure if this is causing the inconsistency of the checking or not. However I'm not quite sure on how to change or edit the security rules efficiently.
Here is my data struct for active usernames if this helps, just displays users uids and the username associated with that uid..
- activeUsernames
6GpUz3iLwmWdDrlMtf7tT0yAULw2
username:
8sRfoqa4dKYzUKGdIZAtwE6NQZH2
username:
SRY4xUmRMgXbLL7GfsxosF6sS1y2
username:
Also here are the security rules
"rules": {
".read": true,
".write": true
}
}
To get rid of the message, change your rules to:
don't forget to separate them by ,
{
"rules": {
".read": true,
".write": true,
"activeUsernames": {
".indexOn": "username"
}
}
}
This tells the Firebase servers to add an index on username under /activeUsernames, which allows it to perform ordering/filtering on the server instead of on the client.
The best approach to this would be to create a cloud function that will check if the username is taken and simply return true or false.
Having your rules set to true means that anyone can read or write in this database and that is very insecure
Using cloud functions that allow to abstract this from your code and be used in any other platform such as android and web
Check the cloud function documentation here on how to setup and call your cloud functions, then you can use the Admin SDK in your function to get full access to the database and query the database etc.
How do I map a Facebook friend id to a Firebase uid in the realtime database? It's my understanding that the Firebase uid is not the same as a Facebook id.
My current user flow is logging into Facebook through the Facebook sdk, and then passing the facebook access token to the Firebase sdk to login with Firebase.
My end goal is to store game scores so that a user can see the scores of their friends as well as their own. I do not want to request every score for every player and filter for this information on the client. I would rather send X queries for X amount of friends and only request the scores desired.
Not sure what the downvote was for, but here is the solution I ended up using.
Storing the scores by facebookId instead of firebaseId allows the lookup of scores of friends, since a facebookId can't be mapped to a firebaseId without storing it in the database anyway. It looks like this:
facebookUsers {
facebookId {
"firebaseId" : firebaseId,
"scores" : {
level : score,
}
}
}
This structure allows you to easily lookup scores by facebookId, and also map facebookIds to firebaseIds if desired for other operations.
To save scores:
FirebaseDatabase.DefaultInstance
.GetReference("facebookUsers")
.Child(facebookId)
.Child("scores")
.Child(level)
.SetValueAsync(score);
To lookup scores:
FirebaseDatabase.DefaultInstance
.GetReference("facebookUsers")
.Child(facebookId)
.Child("scores")
.Child(level)
.GetValueAsync();
To restrict writes based on facebookId, use the following rules:
{
"rules": {
".read": "auth != null",
"facebookUsers": {
"$facebookId" : {
".write": "data.child('firebaseId').val() == auth.uid || (!data.exists() && newData.child('firebaseId').val() == auth.uid)",
}
}
}
}
Hopefully this helps someone more than a downvote.