I'm trying to use Firebase as the backend for a corporate situation where there will be clients who need to have access limited to the content they create; and employees who need to be able to look at all data. Is that possible in Firebase. I could not immediately see how to create such a rule if, say, I had an array of firebase uIds of employees.
Yes, this is definitely possible in Firebase.
Given a simple Firebase structure:
users
uid_0
name: "Rupert"
uid_1
name: "Buffy"
uid_2
name: "Zoran"
admins
uid_2: true
data
uid_0: "something interesting"
uid_1: "look ma, no hands"
uid_2: "Beam me up"
and rules
"data": {
"$uid": {
".read": "auth != null && ( $uid == auth.uid || root.child('admins').child(auth.uid).exists() )"
".write": "auth != null && ( $uid == auth.uid || root.child('admins').child(auth.uid).exists() )"
}
}
This rule will ensure the user is
1) authenticated and
2) the node they are accessing has their uid as a key
or
their uid exists in the admins node.
So Buffy who is uid_1 can only access "look ma, no hands" and Zoran with uid_2 (an admin) can access any node.
This probably could be simplified but it demonstrates the concept.
The Firebase documentation has some recipes one of them shows a specific example of role based security implementation
Related
my current firebase realtime security rules are like below
{
"rules": {
"users": {
".read" : true,
".indexOn": ["email"],
"$user_id": {
".read": true,
".write": "auth != null && $user_id === auth.uid"
}
}
}
}
they translates as only the authenticated user can write the data to his own node under users/
However, we have admin users who should be able to modify the data of non admin users.
The way we identify admin users are a user property isAdmin which is true for admin users. so the sample data with a admin and non admin user looks like below
{
"users": {
"kldjjfjf" : {
"name": "vik", "isAdmin": true
},
"lfllfomr": {
"name": "neeti", "isAdmin": false
}
}
Please advise what is the best practice to handle this kind of usecases? doing a .write true will solve it but then it will make it open to anyone to modify anyone's data.
The simplest ways I've found to allow Administrative access is to:
Use a custom claim for admins
Use a whitelist of admin UIDs
Use a custom claim for admins
You can add custom claims to Firebase Authentication user profiles with the Admin SDK. Claims are custom key/value pairs that you determine the meaning of yourself. The first example from the documentation shows setting a claim called admin to true, for example with the Admin SDK for Node.js:
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
Once a custom claim is set, it is transported to the client when it signs in, and is also available in security rules. You can check the above with:
".write": "auth != null && ($user_id === auth.uid || auth.token.admin === true)"
Use a whitelist of admin UIDs
A simple alternative is to store a list of UIDs in your database of users with specific privileges. For example, you could have a top-level Admins list:
Admins
uidOfVik: true
uidOfPuf: true
The above means that you and me are admins. You then check for those in the security rules:
".write": "auth != null && ($user_id === auth.uid || root.child('Admins').child(auth.uid).exists())"
Here's an alternative:
Firebase security rules only apply to clients connecting normally to the application.
If you have your own back end (I can't assume that, because Firebase is made for Serverless computing) then it can connect to the application with the admin SDK, which bypasses the security rules.
Alternatively, you can make a separate application for your admin users, that will connect to firebase using the admin SDK.
More information: Firebase Documentation - Firebase Admin SDK
I am really confused with Firebase rules and need some help. I googled a lot but actually got only more confused and still don't get it work.
Lets say I have a Firebase db object like this:
root/orders/$id
and inside the $id, I have a child user_id:
root/orders/$id/user_id
Now I want a rule which only allow the user to READ his own orders (but not write anymore in existing once, how ever he need to create new once) and additional I want the users which are admins to READ/WRITE all orders at any time.
I come up with this so far
"orders": {
"$id": {
".read": "root.child('orders').child($id).child('user_id').val() == auth.uid || root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'",
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'"
},
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'",
".read": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'",
".indexOn": ["status", "user_id"]
},
My admins are marked as admins in my user table:
root/users/$id/admin (== user_is_admin)
My intention was for the first part to allow users with the same auth.uid as the requested /orders/$id/user_id to read their orders and for admins to read and write. The admin part is working, but my user has no access for some reason.
The second part was for admins to have read/write access to all orders (without specific $id) which also works fine plus a normal user need the write to CREATE a new order here.
To resume the admin part of my rules works, but the user part does not.
1. my user cant read is own orders
2. my user cant create a new order
I would be really happy if somebody can help me out here.
The Rules:
The following rules should enforce the policies you've outlined in your question:
"orders": {
"$id": {
".read": "data.child('user_id').val() === auth.uid",
".write": "!data.exists() && newData.child('user_id').val() === auth.uid"
},
".write": "root.child('users').child(auth.uid).child('admin').val() === 'user_is_admin'",
".read": "root.child('users').child(auth.uid).child('admin').val() === 'user_is_admin'",
".indexOn": ["status", "user_id"]
}
Note that Firebase security rules cascade, so once you've granted read/write permissions to admins for orders, you don't need to repeat the rules for orders/$id. (Something to remember - although it's not an issue with your rules - is that once you grant a permission on a parent you cannot revoke it on a child.)
The orders/$id/.read rule uses data.child to compare the user_id and the auth.uid. This is the same as your rule, it's just a little shorter and does not include the admin check (which cascades).
In addition to checking that newData (the value being written) contains the user_id, the orders/$id/.write rule checks to see that data (the previous value) does not exist. That will allow creates, but will deny updates and deletes.
Orders for Users:
As noted in your comment, it's not possible for a user to query a list of orders under the orders key, as the user won't have permission to read the orders of other users (they'd need to be readable to be filtered out). You could solve the problem by storing a mapping of orders by user, like this:
"orders": {
"order_1": {
"user_id": "user_1",
...
},
"order_2": {
"user_id": "user_1",
...
},
"order_3": {
"user_id": "user_2",
...
}
},
"ordersByUser": {
"user_1": {
"order_1": true,
"order_2": true
},
"user_2": {
"order_3": true
}
}
You can use Firebase's multi-location updates to make maintaining the mapping less tedious. For example, to add another order for user_1, you could do this:
firebase.database().ref().update({
"orders/order_4": {
"user_id": "user_1",
...
},
"ordersByUser/user_1/order_4": true
});
This would let users obtain a list of order IDs (and, from those, the orders themselves) and admin users could still obtain a list of all orders, etc.
Also, you should include a rule so that users can only read their own order IDs under ordersByUser, etc.
I have following scenario for my games sign up process:
User signs up with email and password and is asked to verify the account. After verification their account is created. User is redirected to character creation process, they will also be redirected there if they log in and have not yet created character. After user creates character they are directed into the game, now as they have character they will also be logged in straight into game.
So I now need an entry inside my user: {} in firebase database that tracks this character creation state i.e. user: { status: 'CREATING_CHARACTER' } and user: { status: 'CHARACTER_CREATED' } that will let me know what screen they need to be shown after login. This state should only be able to update by admin I believe i.e. user can auth and should not be able to set this unless they actually go through character creation process and complete this.
I am struggling to figure out how this flow would be handled in terms of security in firebase.
Try something like this:
"users": {
"$uid": {
"status": {
".read": "data.root().child('admins').child(auth.uid).exists() ||
$uid === auth.uid",
".write": "data.root().child('admins').child(auth.uid).exists() ||
($uid === auth.uid && data.exists() && data.val() !== newData.val())"
".validata": "newData.val() === 'CREATING_CHARACTER' ||
newData.val() === 'CHARACTER_CREATED'"
}
}
}
users can read their own status, or if they are in "admins" group
users from "admins" group can write status
users can change their status if it already exists and it's different then existing one
status can be set to CREATING_CHARACTER or CHARACTER_CREATED
You'll have to check if user finished creating character on the client. And if you want Admin only from firebase console, remove parts with 'admin' users from these rules.
I have the following form of database
Database
users
<UID>
user
<other information>
I am trying to read all my users in admin mode and only allow individual users to access their own information.
I am trying this rule:
"users": {
"$uid": {
".read": "auth != null && (auth.uid == $uid || root.child('users').child(auth.uid).child('user').child('admin').val() == true)",
".write": "auth != null && !newData.child('admin').exists() && (auth.uid == $uid || root.child('users').child(auth.uid).child('user').child('admin').val() == true)"
},
".indexOn": ["userid"]
},
I am doing the following query and I see the following error:
allusers = $firebaseArray(firebaseDataService.root.child('users'));
permission_denied at /users: Client doesn't have permission to access the desired data.
Any idea what I am doing wrong here ?
When you attach a listener to /users, the Firebase server checks whether you have read permission on /users. If not, it rejects the listener.
This is a common source of confusion for developers new to Firebase. Coming from a SQL/relation mindset, we are used to use security to perform (server-side) filtering. In Firebase you cannot use security rules to filter data.
I'll add a few relevant links below for further reading:
rules are not filters in the Firebase documentation
Restricting child/field access with security rules
the Firebase blog post on why denormalizing is normal
an article on airpair about data structuring
a great summary from a while ago on the firebase-talk group
I've been looking on the docs but I couldn't figure out how to prevent duplicated entries if the email exist on a record. There are my current rules
{
"rules": {
"users": {
"$uid": {
// grants write access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth !== null && auth.uid === $uid",
// grants read access to any user who is logged in with an email and password
".read": "auth !== null && auth.provider === 'password'"
}
}
}
}
And my record format is:
Thank you very much
Unfortunately you cannot do this type of query in firebase due to it's distributed nature. In general, arrays are extremely tricky and you can read about their limitations in the context of Firebase here.
The way I see it you have two options, you can index your users "array" by the email itself, or you can keep a completely separate object holding all the emails in the system to check against when you make an insert. My suggestion would be the first, set the user object to users/<email>.