Firebase: Cannot add user entry (`set` permission denied)

Firebase: Cannot add user entry (`set` permission denied),firebase,permission-denied,firebase-security,Firebase,Permission Denied,Firebase Security,I want to ensure that a new user can be created (from the client), but only an authenticated user can read or write an existing object. I have a simple rule set: { "rules": { "users": { "$uid": { ".read": "auth != null && auth.uid === $uid", ".write": "!data.exists() || auth.uid === $uid" } } } } I am calling createUser and then in the callback I'm trying to add an entry to my own users object: const usersRef =

I want to ensure that a new user can be created (from the client), but only an authenticated user can read or write an existing object.

I have a simple rule set:

{
    "rules": {
        "users": {
          "$uid": {
              ".read": "auth != null && auth.uid === $uid",
              ".write": "!data.exists() || auth.uid === $uid"
          }
        }
    }
}

I am calling

createUser
and then in the callback I'm trying to add an entry to my own
users
object:

const usersRef = ref.child('users');

const userEntry = {
    [userData.uid]: {
        created: new Date().getTime()
    }
};

usersRef.set(userEntry)

I would have thought that even though the user is not yet logged in, they should have write permission because of

!data.exists()
. Yet I am getting a
PERMISSION_DENIED
error.

If I set

".write": true
on the
users
level then it will cascade (and override?) my inner rules won't it?

Edit:

This fails even with:

"users": {
  "$uid": {
      ".read": true,
      ".write": true
  }
}

Thanks.


#1

I think I initially misunderstood the problem. In your current callback, you are trying to overwrite the entire users level because of how set works.

You would really want to set only the thing that doesn't exist:

const userRef = ref.child('users').child(userData.uid);

const userEntry = {
        created: new Date().getTime()
    };

userRef.set(userEntry);

Then, I think that your existing rules would work.


#2

I think this a confusing question because creating a user and writing to the database are completely different things. So i will just show how i do it in my app.

  1. First step is creating the user
  2. Next log the user in because creating doesn't automaticly log the user in (I do this in the callback function of create user)
  3. Last step is writing the user data to firebase

I use the following rule to make sure each user can only write to his own node in firebase (documentation):

{
  "rules": {
    "users": {
      "$user_id": {
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

And one thing to keep in mind is that set() will replace any existing data at that path. So make sure you use the uid of the user and not the users node.

Finally i want to point out a huge flaw in the rules you posted in your question:

".write": "!data.exists() || auth.uid === $uid"

This rule states you can write if there isn't any data yet OR you have the correct uid. The first part of this statement is the problem because ANYONE can write to this locaion when there isn't any data. Also because $uid is a dynamic path you can add anything there like:

"users": {
  "WoWIjustMadeThisUp": {
    "nice": "Some huge value making you go over your limit"
  }
}

If you want users to only write an initial value and after that won't be able to edit it just use a validate rule to check if there is already data at that location.


#3

"If I set ".write": true on the users level then it will cascade (and override?) my inner rules won't it?" Yes

#4

@FrankvanPuffelen actually this fail even if I set both read & write to true - it only works if I move it above to the users level - any ideas?

#5

@DominicTobias Why do you check for existing data? What is your exact usecase here? If we know what behavior you are trying to have we can help you better.

#6

@AndréKool good point I added it to the top of the Q

#7

Thanks that makes sense, looks like it'll work I'll just verify later/tomorrow cheers

#8

Well it made sense but it still doesn't work, actually even removing the .write rule in the $uid and only leaving the !data.exists() outer one still doesn't work :/

#9

@DominicTobias , I didn't originally follow that this was in a callback after you create the user, and that your code looks like it would replace the entire user level? I've updated my answer with how I would try to do it.

#10

Firebase is a little confusing in that it has a managed user table that's not modifiable, so in order for my Node app to know a user has been added I also need to add an entry to my own user table - so they are in fact two different tables! Sorry for the confusion

#11

@DominicTobias , Ah then I think it is just that your code sets at the users level instead of users/username level, I've updated yet again.. :)

#12

The problem was that I needed to do ref.child('users').child(userData.uid) But I think you're right - it's better to prevent public adds to users and just create it when the user first logs in