Allowing specific user write access - firebase

I'm kind of stymied on my Firebase read/write rules. I wish I had a way to set break points as it's checking authentication (there isn't a way, is there?).
My question is fairly simple and I feel like I should be able to understand it better. I feel like most of stems from not understanding fully the rules. This information is for a simple way to view products online and the owner needs to be able to add products. So I want the world to be able to read and people who log in be able to write.
My data looks like:
app
Users
email: xxx#xxx.com
product
0
name: pizza
And my rules:
{
"rules": {
"product":{
".read": true,
".write":"root.child('Users').child(auth.uid).child('email').val() === 'testuser#test.com'"
}
}
}
and I don't have a great grasp of what this means. I really just want to say, if the user is logged in, allow write capabilities. I would have hoped I could so something like ".write": "auth" meaning that if auth exists (user is logged in) then let him write. But I'm clearly not fully understand how the rules work. I'm using simple email/password authentication.
How are these rules tested and how do I pass parameters to them?

I'll try to explain your .write security rule, which is:
"root.child('Users').child(auth.uid).child('email').val() === 'testuser#test.com'"
This is indeed not the easiest rule to start with and the Firebase documentation admittedly dives into the deep rather quickly.
After I log on to my application with twitter, my code will get an authData variable that tells it:
authData
uid: "twitter:4511241"
provider: "twitter"
id: "4511241"
There might be a few more properties, but this should be the gist of it. The authData will typically contain a child object with properties that are specific to the provider, so in this case twitter and you can normally find some pretty interesting stuff there. Such as in the case of Twitter, the user's twitter name.
authData
uid: "twitter:4511241"
provider: "twitter"
id: "4511241"
twitter:
userName: "puf"
email: "puf#twitter.com" // note: this is just an example email address,
// it doesn't exist (as far as I know)
This object is made available to my code, it is not stored anywhere in Firebase by default.
In your security rules, you can check against the same structure. But only a minimal subset of the information is available here, just enough to identify the user. The Firebase documentation for the auth object names these properties:
provider | The authentication method used ("password", "anonymous", "facebook", "github", "google", or "twitter").
uid |A unique user id, guaranteed to be unique across all providers.
With these properties, we can already allow a specific user write access to our data. Like I said in my comment to your answer:
".write": "auth.uid = 'twitter:4511241'"
This will allow me to write data when I log on with my #puf twitter account. But unfortunately it doesn't allow me to check against more user-friendly properties, such as the email address.
A common practice is to create a top-level Users node in your Firebase and add all the information for all the users under that node, identified by their uid. So in my case above:
Users
twitter:4511241
userName: "puf"
displayName: "Frank van Puffelen"
email: "puf#twitter.com"
github:913631
userName: "puf"
displayName: "Frank van Puffelen"
email: "puf#github.com"
I left the provider and id off here, since they don't provide much value for the example
When you know a user's uid, you can look up his additional information in this node. And that is exactly what the security rules you had before does:
"root.child('Users').child(auth.uid).child('email').val() === 'testuser#test.com'"
So this will look from the root of your Firebase, for a node called Users, then for the uid of the logged in user and under that his/her email address.
The cool thing about checking for a specific email address, is that it is not tied to the user's provider. So no matter whether the user is logged on with twitter, github, facebook or some other supported provider, as long as they used the same email address for each account, they will be able to write the data.

Doing the below seemed to do the trick. If the auth variable exists, then allow write abilities. Due to me not fully understanding these rules, I have to wonder if this is running against the current user or if it's checking to see if any user is logged in.
{
"rules": {
"product":{
".read": true,
".write":"auth !== null"
}
}
}

Related

accessing email address in firebase rules [duplicate]

This question already has an answer here:
How can we guarantee that the email saved by the Firebase user is indeed his own email?
(1 answer)
Closed 6 years ago.
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:
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
The user authenticates via google. On the client side we can see their email address in auth.user.email
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.
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.

How to implement collaborator invitations using firebase?

I am developing an app with firebase and angular and angularfire. I would like to implement a
"invite collaborators" feature in much the same way as firebase itself implements collaboration - that is, the app user can enter an email address to invite collaborators which would send an email and generate an "inviteToken", just as is done when inviting collaborators in firebase itself.
I understand that security rules (to limit collaborator access) and schema design( a /collaborators 'folder' ? ) are one aspect, which can be accomplished using native firebase and angular. My question is how to best implement the invite email and the 'inviteToken'? What would be the most expedient way to implement such an invitation feature? Could it be done using native firebase? Or would one need to implement separate, server side code (nodejs?)? Perhaps someone from the firebase team can opine based on how firebase itself implements collaboration.
You can implementation collaboration by hashing the email address of the user you want to share a particular piece of data with, and storing it under a permissions field.
For example, let's start with a path /items/item1 that is owned by 'user1':
{
"items": {
"item1": {
"data": "foobar",
"permissions": {
"user1": true
}
}
}
}
You'd set the security rules for the data as follows:
{
"rules": {
"items": {
"$item": {
".read": "data.child('permissions').hasChild(auth.uid)",
".write": "data.child('permissions').hasChild(auth.uid)"
}
}
}
}
Now when 'user1' wants to share 'item1' with 'user2', they will simply write the value 'user2' and set it to true under the permissions key. You can extend the structure of the 'permissions' key to be as granular as you want (eg: collaborators can only read, but owner can both read and write, etc.)
In practice, you may want to use hashes of the user's email addresses, for example. Also take a look at Simple Login for an easy way to authenticate your users (once authenticated, the auth variable used in the security rules above are automatically set for you).

How do I modify the user properties managed by FirebaseSimpleLogin?

It seems like the only thing that can be changed is the password (via auth.changePassword()). How do I let a user change their email address or display name?
The firebase Auth object is pretty simple but it will provide you the user id generated when the user authenticates to your system. You would then take this user id and map it to a Users location where you can store additional information such as display name.
For example, after the user has authenticated and you have your auth object with id value, you could do:
new Firebase('https://your_fb_url.firebase.io').child('users/'+id).set({email: email, name: name}, function(err) {})
You'd want to have read/write rules setup on that location to only allow the authenticated user to see & make changes. Something like:
{
"rules": {
"users": {
"$user": {
".read": "$user == auth.uid",
".write": "$user == auth.uid",
}
}
}
}
6/12/2015 - UPDATE - Below is Outdated
As for changing the actual login e-mail (for Firebase Simple Login Web), that I'm not so sure about. I know they provide a change password method but I haven't seen any documentation about a change login/email method.
The underlying code for firebase simple password doesn't appear to include any methods for changing the login e-mail address associated with the account. The changePassword method eventually performs a jsonp call out to /auth/firebase/update with the email, old password, and new password.
I'd hate to suggest using a combination of removeUser/createUser to remove the old account, create a new account, and update any user id associations you have you in your app - but I don't see a straightforward "changeEmail" method. The remove/create route would require the user to enter their password again - though that's a pretty common practice for updating logins these days anyway.
6/12/2015 - UPDATE - New API
Firebase has moved away from Firebase Simple Login as a separate module and now the core Firebase 2.x library has authentication related methods baked in. Including a method to change the e-mail account used for the authWithPassword methods.
See updated 2.x docs for changeEmail()

how do I implement role based access control in firebase

This is my first foray into Firebase & nosql, I come from a SQL background.
Using Simple Login Security Email/Password, how do I limit access to data in Firebase? For example, some user will have access to create a business object (users, customers, categories, etc), others won't. Is there a way to attach a list of permissions to the "auth" variable?
There isn't a way to attach permissions directly to the auth variable (or at least that doesn't seem to be an intended strategy). I'd recommend creating a collection of users organized by auth.uid and you can keep whatever kind of permission attributes you want in there, such that your security rules might something look like this (untested):
{
"rules": {
".read": true,
"users": {
".write": "root.child('users').child(auth.uid).child('role').val() == 'admin'"
}
}
}
Where role is an attribute belonging to all objects in your users collection.
UPDATE
See comment below:
"There isn't a way to attach permissions directly to the auth variable" This changed in 2017. You can now attach custom claims to an auth profile, which are available in security rules. See bojeil's answer and the Firebase documentation for custom claims. – Frank van Puffelen
Firebase launched support for role based access on any user via custom user claims on the ID token: https://firebase.google.com/docs/auth/admin/custom-claims
You would define the admin access rule:
{
"rules": {
"adminContent": {
".read": "auth.token.admin === true",
".write": "auth.token.admin === true",
}
}
}
Set the user role with the Firebase Admin SDK:
// Set admin privilege on the user corresponding to uid.
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.
});
This will propagate to the corresponding user's ID token claims.
You can force token refresh immediately after: user.getIdToken(true)
To parse it from the token on the client, you need to base64 decode the ID token's payload: https://firebase.google.com/docs/auth/admin/custom-claims#access_custom_claims_on_the_client
You can upgrade/downgrade users as needed. They also provided a programmatic way to list all users if you have recurring scripts to change a users' access levels: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users
Looking at this again a year later "Custom Tokens" may be a better option.
https://www.firebase.com/docs/security/guide/user-security.html#section-custom

Securing Firebase CRUD operations with users

Due to the thin AngularFire documentation and the differences between it and the default web documentation for Firebase, I'm a little lost on how best to secure Create, Read, Update, and Delete operations with users.
In short, say I have an application that manages stores. Users can be owners of the stores or patrons. Owners should read and edit their own stores in their view and patrons should read all but edit no stores in their view.
I'm concerned about the security of suggested methods by Firebase docs such as
So for example, we could have a rule like the following to allow users
to create comments as long as they store their user id with the
comment:
{
"rules": {
".read": true,
"$comment": {
".write": "!data.exists() && newData.child('user_id').val() == auth.id"
}
}
}
To me, this means that I could hack my application's data by simply passing in my victim's user id when I want to post a comment as them. Am I wrong?
I've read the security documentation thoroughly, several times. I think I need further explanation here. Identifying by a client-exposed parameter is the only method I can find so far.
In the example shown here, auth refers to the authenticated user's token data. This is a special variable set by Firebase during auth() events, and thus not something you could hack at the client. In other words, you would only be able to write a comment if you set the user_id value to your own account id.
The contents of the auth object depend on how the client authenticates. For example, SimpleLogin's password provider puts the following into the auth token: provider, email, and id; any of which could be utilized in the security rules.
It's also possible to sign your own tokens from a server, and of course the sky is the limit here.
But the bottom line is that the token's internal values are provided by a trusted process and not by the client, and thus cannot be altered by a user.

Resources