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.
Related
The following is the database structure and i want to make sure the owner of the comment whose user_id is part of the comment object to have read and write access to the comment and all other users have read access to comment and the ability to like it to increase like_count:
The following is the security rule I came up with:
{
"rules": {
"comments": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().child('users').child(auth.uid).val() != null && newData.parent().child('comments').hasChildren().hasChildren().child('user_id').val() == auth.uid)",
"$commentId": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().parent().child('users').child(auth.uid).val() != null && !newData.parent().parent().child('comments').hasChildren().hasChildren().child('like_count'))"
}
}
}
}
So, for read better will be:
".read": "root.child('users').hasChild(auth.uid)"
".write" is more complicated:
You need to only allow edit in like_count, and you need to allow for only one like per user
Proper way to do this will be expand structure of your "post", something like like_list, here or in other place to protect data transfer from growing when post will be download by client
So in "comment" node you want to allow every auth user to .write and .read, they will be able to add new comment then (upper rules will be fine for this).
For $commentId node rules will look like:
".write":".data.child('user_id').val() == auth.uid"
You can link like_list to a clinet by adding user_id to the "like / $ commentId" node and setting listener in firebase funtions for this node. adding new user_id will fire "write" event and then call function to secure increase the value of like_count.
You can archive it with a firebase-function and transaction.
https://www.tutorialspoint.com/firebase/firebase_write_transactional_data.htm
If you really don`t want to change schema then you need to secure every single child of comment, like:
"$commentID":{
"comment":{ ".write":"".data.parent().child('user_id').val() == auth.uid" ),
...//things allowed to edit by owner
"like_count":{
".write":"root.child('users').hasChild(auth.uid)",
".validate":"newData.val() == data.val() + 1"
}
}
But this will not secure post from giving more than one like per user.
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'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>.
got stuck on this problem: I want to allow user to register on my webpage using firebase, for that reason I planned the following structure:
users : {
user0 : { //user specific data },
user1 : { //... }
...
}
Everything works fine (writing, reading...) until I change the security rules. I want to change them because I want the users only to register and not to have the power to delete their or potentially other user accounts. But I couldn't find anything very helpful on that. Below is what I'm currently left with:
{
"rules": {
"users" : {
".read": true,
"$uid" : {
".write": "auth !== null && auth.uid === $uid"
}
}
}
}
I'm wondering how to set up the rules such that users can only add new accounts.
I would appreciate any help!
Thaanks :)
Edit: Here's what I wanted to achieve with the rules above, maybe the below example using the simulator will make my point clear.
The first thing, I want to do is, is to let a user register at the /users node. Therefore, I chose the Custom Auth point in the simulator:
{ provider: 'anonymous', uid: '002a448c-30c0-4b87-a16b-f70dfebe3386' }.
Now, if I choose "simulate write" and give the path /users and the following key-value pair:
{ "002a448c-30c0-4b87-a16b-f70dfebe3386": { "1" : {"a" : "b"}}}
I get the below message (see Result_2), which tells me, that I cannot write to /users because there's no write rule allowing it, which I totally understand in the above security rules configuration, but I don't know how to change them such that I am able to write key-value pairs as the above one while still allowing each user to write on there entry only. I.e. the user with the uid: 002a448c-30c0-4b87-a16b-f70dfebe3386 would be allowed to write on the corresponding value with the rules above as long as he is authenticated (see Result_1).
E.g. Custon Auth authenticated user writing ON HIS ENTRY: (WORKS PERFECTLY AS EXPECTED)
{ provider: 'anonymous', uid: '002a448c-30c0-4b87-a16b-f70dfebe3386' }.
As the previous time. Now, "simulate write" on path:
/users/002a448c-30c0-4b87-a16b-f70dfebe3386
Result_1:
Attempt to write {"4":{"name":"fred"}} to /users/002a448c-30c0-4b87-a16b-f70dfebe3386 with auth={"provider":"anonymous","uid":"002a448c-30c0-4b87-a16b-f70dfebe3386"}
/
/users
/users/002a448c-30c0-4b87-a16b-f70dfebe3386:.write: "auth !== null && auth.uid === $uid" => true
Write was allowed.
Result_2: Writing the user onto the /users nodes fails, i.e. no registering is possible. And I want here to be able to add a user to /users but not be able to modify/delete user from /users. See simulator output below.
Attempt to write {"002a448c-30c0-4b87-a16b-f70dfebe3386":{"1":{"a":"b"}}} to /users with auth={"provider":"anonymous","uid":"002a448c-30c0-4b87-a16b-f70dfebe3386"}
/
/users No .write rule allowed the operation.
Write was denied.
Permissions cascades - once you give a user a permission on /users you cannot remove that permission on /users/$uid anymore.
The solution is to only grant permission on the lowest level, so in your case:
{
"rules": {
"users" : {
".read": true,
"$uid" : {
".write": "auth !== null && auth.uid === $uid"
}
}
}
}
I've encountered a strange behavior of the Firebase simple login with email/password:
If I login with an existing user account I'm able to write to a Firebase ref (i.e. $root/list/$item).
If not, I have no write access as expected (Firebase rules seem to be OK),
BUT if a client is logged in, and I meanwhile delete a user from Firebase Forge (Auth page), the connected client has still write access to the Firebase ref!
Is it by design or is it a bug?
Thanks.
here are the rules:
{
"rules": {
".read": true,
"list": {
"$item": {
".write": "auth != null && newData.child('author').val() == auth.id",
".validate": "newData.hasChildren(['author', 'content'])",
"author": {
".validate": "newData.val() == auth.id"
},
"content": {
".validate": "newData.isString()"
}
}
}
}
}
Short answer: by design, or more accurately, not applicable in this case.
During auth, FirebaseSimpleLogin generates a token. Once the token is given to a client, it remains valid until it expires. Thus, when you delete the user account in simple login, this does not somehow go to the client's machine and remove the token. This is a pretty standard auth model, and the expiration length on the token (configurable in Forge) is the key constraint for security.
If you want to revoke logins immediately, then simple login is not the right tool for the job. You'll want to use custom login and generate your own tokens. There are some great discussions on revokable tokens, so I'll defer you to those, since that's outside the purview of your question.