5

I'm using firebase 3. When writing firebase rules, the auth object only contains the uid and the provider. Is there any way that this could be enhanced to also provide the email address?

The problem that I'm trying to solve is that the owner of the site I'm working on wants to permission users based on their email address, because he won't know their firebase uid up front.

I have seen solutions to this suggesting to persist the user object in firebase (with the email) and then use that as a reference point in the rules. The problem I can see with that is that if someone knew the email address of a user with full privileges, it would be fairly easy to debug the code, and manipulate the email address prior to saving into firebase, which means it would save their firebase id alongside someone else's email address.

The only way I can see to make this safe is to have the email address provided in the auth object in the firebase rules, which can't be hacked.

Am I missing something?


MORE INFO


The idea is that we can control access to data for a specific location by adding the location name to a user's email address:

  1. A user is created ahead of time manually by the site manager, providing access to a subset of data. e.g
-users
  -user1Email
    -locations
      -someLocation:true
      -someOtherLocation:true
  1. The user authenticates via google. On the client side we can see their email address in auth.user.email

  2. In the rules, I want to do something like

locations : {
  "$location": {
      ".read": "root.hasChild('users/' + auth.email + '/locations/' + $location)",
   }
}

I know I need to escape the email address, just trying to keep it simple for now.

I've tested this out in the simulator and it works perfectly if I use a custom provider and provide the email in there, but using google the "auth" in the rule only has uid and provider properties, not email.

The alternative (other than using a custom provider) is to allow the user to create their account first, and then the locations are added to each user using their uid as the key rather than their email address, but the owner wants to be able to set it up ahead of time so that the first time they log in it words straight away.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
John
  • 121
  • 1
  • 6
  • could you describe the exact use case you are trying to handle? What do you mean by "he won't know their firebase uid up front"? Will the users be authenticated when they attempt to do whatever you are expecting? – adolfosrs Jul 05 '16 at 11:56
  • sorry took me a sec to format the code nicely, but should be readable now. – John Jul 05 '16 at 12:30
  • The email address is now quite often available in the `auth` variable in your security rules. See http://stackoverflow.com/questions/37986097/how-can-we-guarantee-that-the-email-saved-by-the-firebase-user-is-indeed-his-own – Frank van Puffelen Jul 05 '16 at 16:17
  • @FrankvanPuffelen that is great, exactly what I need. One question though.. I tried to strip out the "." and "@" from the email address, since that is how the existing data is held in firebase, e.g auth.token.email.replace('.','') etc... This doesn't seem to work though. What is the type of the token.email value? is there some way I can convert it to a string rather than using "matches"? Thanks – John Jul 05 '16 at 18:00
  • As far as I know it is a string, which is why I can call `matches()` on it. – Frank van Puffelen Jul 05 '16 at 18:14

1 Answers1

1

Firebase team is still working to provide the email in the auth object and you can find it with some limitations using auth.token.email in your rules. Please take a look in this post to get more details.

If the current firebase solution doesn't handle all your needs there is some options to workaround.

Since you want to keep your current /users structure you could, whenever registering a new user, link the user uid to the corresponding email in a new branch /user_emails that will simply store $uid: email. Then your rules will look like the following.

 "user_emails": { 
    "$uid": {
       ".write": "auth.uid == $uid",
       ".validate": "!root.child('Users').hasChild(newData.val())"
    }
  },
  "locations": {
    "$location": {      
      ".read":  "root.hasChild('users/' + root.child('user_emails').child(auth.uid).val() + '/locations/' + $location)"
    }
  }

Keep in mind that you will need to enhance them to ensure that only the right users will be able to edit this new user_emails branch.

Community
  • 1
  • 1
adolfosrs
  • 9,286
  • 5
  • 39
  • 67
  • When you say link the user uid to the corresponding email, do you mean do that manually. or programatically after the user logs in? If programatically, I don't think that solves the security issue, since that code could be invoked maliciously, providing the email address of a user that is known to have full access. Then, the new firebase id would be linked to the user with full access. If manually, then it still means we have to let the user log in without access, then manually add them, which is what I'm trying to avoid. – John Jul 05 '16 at 13:29
  • @John Using firebase rules you will ensure that the only branch that the user will be able to edit is the one with his uid as the key. Just added an example rule you can add to the `/user_emails` branch. – adolfosrs Jul 05 '16 at 13:34
  • @John And yes. I mean programatically. Whenever you register a new user to `/users` calling `createUser` or whatever you should make sure you call something like `ref.child(uid).set(email)` in the callback. – adolfosrs Jul 05 '16 at 13:38
  • I understand I can protect the user_emails to a particular user, but I still don't think that helps. Say I happen to know that a user with email address admin@this.com has access to all locations... If I registered a new firebase account, and hacked my email address just prior to calling ref.child(uid).set(email) to be admin@this.com, it would put the super user email address against my firebase uid - and now I have access to everything in the system. See what I mean? – John Jul 05 '16 at 13:52
  • @John you will have the same email but you will not have the proper uid linked to that email since you will be passing the authenticated user uid. When you use `auth.uid` in the rules it means that the user is authenticated with facebook or whatever. So, if you are authenticated and try to set the email with other uid that its not your current authenticated uid it wont save because of the rules. The only way to "hack" it is having the credentials for the other user. – adolfosrs Jul 05 '16 at 14:00
  • @John when you try `ref.child(uid).set(email)` passing the uid thats not your uid. The expression `auth.uid == $uid` will return false and you wont be able to `.write`. – adolfosrs Jul 05 '16 at 14:11
  • what I mean is, I would be allowed to update the user_email record for my uid (since I am me) and store someone else's email address against it. The rules you specify above would allow that, and then since the permissions for locations are governed by whether the email connected to the current uid has each location, I can see everything. If my uid is 123, and the admin@this.com is 456, you could have both those ids in user_email with a value of admin@this.com. Both uids would then be able to see all locations that admin@this.com can see. – John Jul 05 '16 at 14:14
  • @John Now I see your point. You will be able to cover it by making sure that the linked email in `/user_emails` doesnt exists yet in the `/users`. So keep in mind that, when registering a new user, you will have to link the email before saving the user data in `/users`. Just added the rule to cover this scenario. – adolfosrs Jul 05 '16 at 14:55
  • ok, I think we've gone full circle now :) The idea was to avoid having to go and manually link anything after the user logs in the first time, by setting them up ahead of time in the users/ section using their expected email address. If we need to manually link after the user creates their account, we might as well just use uid. Thanks a lot for your input, it's been really useful to talk it through with someone. Just a shame firebase doesn't provide that email address to begin with in the auth object. Thanks again – John Jul 05 '16 at 15:27
  • @John Actually you wont have to link the email manually as I said before. After creating the user in firebase you will link the email in `/user_emails` and then create the `/users/email`. You can do this entier process using the firebase sdk. There is no need for manually work. Hope my toughts was useful and my bad for missing the detail about the user refering to whatever email. Thank you and good luck. – adolfosrs Jul 05 '16 at 15:38
  • 1
    @adolfosrs "firebase don't provide the email in the auth object" the email address is now (often) available in the security rules and to code. See http://stackoverflow.com/questions/37986097/how-can-we-guarantee-that-the-email-saved-by-the-firebase-user-is-indeed-his-own – Frank van Puffelen Jul 05 '16 at 16:16
  • @FrankvanPuffelen Sorry Frank, this is pretty new for me. Thanks for pointing it out. Just edited the question clarifying this. – adolfosrs Jul 05 '16 at 16:25
  • It's horribly missing from the documentation, so not surprising that you missed it. ;-) – Frank van Puffelen Jul 05 '16 at 17:02