One Firebase, multiple company clients

2,280 views
Skip to first unread message

visdyn

unread,
May 30, 2015, 9:29:54 AM5/30/15
to fireba...@googlegroups.com
We are looking for a solution to a problem:  how to handle authentication & user creation for multiple company clients, each client with their own users, within one Firebase. This is a desktop/mobile app.

Some details:

Each 'client' is actually a unique company, unrelated to other clients (companies), and do not share data nor users.

Authentication cannot rely on other products; Facebook, gmail etc is not an option, so email & password authentication is the login.

Assume our app is called HappyApp. Here's the generic structure

HappyApp
   
Client_1_Node
         
Users
             some_uid
's
         Data
             some_data
    Client_2_Node
         Users
             some_uid'
s
         
Data
             some_data

Pretty straight forward.

Here's the flow.

A potential user says 'Hey, I want to use HappyApp in our company because we want to be a happy company'.

The initial user (usually the company owner or head IT person) download's the app and is presented with an interface that allows him to enter their company name, other info, their email and a desired password.

The user is created in Firebase, their company data stored in a Client Node and can then login and use HappyApp. Because they are auth'd, they can now create app users so the other employees in their company can also login and use HappyApp and access their company data.

Here's the question:   how does that initial user for this company get created?

The issue:    a user cannot be created (in code) in firebase without the current user (the one that doing the creating) being auth'd.



Option 1: Hard code a master user into the app (the non-secure option)

The app has a 'master' user (user name & password) stored in code that can be used to authenticate to Firebase, and then create the client data; the new user along with the Client_1_Node and associated data. It's a simple solution but hard-coding user names or passwords in code is a bad idea.


Option 2: Roll an authentication server

A separate authentication server app (we'll call it auth_app) is rolled (along with hardware) that observes a 'new_user_request' node in Firebase that doesn't have security on it.

The auth_app has a 'master' user already auth'd and logged in (and always online) so it can create users and nodes in firebase as new clients (companies) download and start to use the app.

The sole job of the auth_app is to listen for new user requests (for new companies) and create that initial user and their client node in Firebase. The server could also be leveraged to email a user a temporary login password for the users initial login.

With this option, the user app writes a user request to firebase, the auth_app observes that request and then can create the new user in firebase, write out the Client_1_node data and then respond to the user app (via a firebase node the user app is observing) to tell it auth and log in

This option works but requires a server app and server hardware.


How can the initial user be created in firebase without either having a 'master' user hard-coded into the app or having a separate server to handle creating the initial user?

We don't think this is beyond the capability of Firebase but just not sure how to handle it.

Suggestions on how to create that initial user for each client (company)?

What's a better way to handle multiple clients (companies) within one firebase?


Jacob Wenger

unread,
Jun 1, 2015, 8:46:36 PM6/1/15
to fireba...@googlegroups.com
Hey there,

Before I go into answering this, can you clarify this comment:

"The issue:    a user cannot be created (in code) in firebase without the current user (the one that doing the creating) being auth'd."

What do you mean by that? Users can definitely be created without being authenticated. Or else you'd never be able to make a user. I think you are wondering how you add a user to a company. You need to bootstrap that company some way. You probably want to create some admin user which only you have access to which has write access to some sort of /companyUsers/ node where you can add people who belong to certain companies. This definitely won't require you to roll your own auth server though. But maybe I am misunderstanding what you are trying to do...

Jacob

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/fb92f7a7-293b-4bc8-8f3b-0262dd1aa454%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

visdyn

unread,
Jun 2, 2015, 9:18:52 AM6/2/15
to fireba...@googlegroups.com
Thank you for catching that, and let me clarify. We have a Users node that when a Firebase user is created, their uid etc it added to the users node. And our rules are such that, unless they are authenticated and exist the users node, they cannot read/write any data. It would obviously be a security issue to just let anyone create users and access data.

"Users": {
   
".read": "auth != null",
   
".write": "auth != null"
}


You need to bootstrap that company some way. You probably want to create some admin user which only you have access to which has write access to some sort of /companyUsers/ node where you can add people who belong to certain companies. This definitely won't require you to roll your own auth server though. But maybe I am misunderstanding what you are trying to do...

Ah, therein lies the rub.

Having potentially hundreds of companies and numbers of employees per company, this needs to be an automated process wherein the company owner gets our app, creates their initial master (owner) account and can then add/create the employees for their company who will then be able to log in an use the app for their company data.

Having an 'admin user', which is what we have now, who can create users is where we are at currently, but that leaves 3 options (so far)

1) A human, the admin user (me), must create each user for each company manually.

2) We hard code the admin users credentials into the app code, it authenticates and then the master user (owner of the company) can then be created. It's the simplest solution but hard coding user name and password into an app is typically a bad idea. Any thoughts on this?

3) Roll an admin server as I mentioned above that is hardware and a small server app that creates the master (owner) users in firebase on the fly.

We are wide open as to other options, or is this simply beyond the scope of Firebase?

Jay

unread,
Jun 5, 2015, 4:51:05 PM6/5/15
to fireba...@googlegroups.com
I'm a little surprised that there has not been more feedback on this topic.

I would think that developing a business level app that supports multiple independent companies with a cloud based back end would be something developers would be driving towards.

Is there another way to approach this challenge with Firebase that we are overlooking?


Doug Thompson

unread,
Jun 5, 2015, 7:37:02 PM6/5/15
to fireba...@googlegroups.com
Agreed Jay - we've been struggling with the same issue...would love to hear other thoughts.

Kaue Machado

unread,
Jun 7, 2015, 12:21:55 PM6/7/15
to fireba...@googlegroups.com
I have a somewhat similar model to yours. I have put a lot of thought into the model, and probably changed it a dozen times until I came with a good solution.
Basically, you need to denormalize your data a little bit, there are some great advices about this in the getting started docs, and Kato wrote some good articles about data denormalization "relational based":
https://www.firebase.com/docs/web/guide/structuring-data.html

This content should help you think your data from a different perspective as much as it helped me.

I created simple gist to show how your data could be structured:

So with this approach you will have a "many to many" relation of sorts, you have many users to many companies. A employees child in the companies node is just an index of all the companies employees, it doesn't have any user data and it's meant only to manage permissions. The companies child in the users node is an index of all the companies the user is part of. This is the same model used by most SAAS, Trello, GitHub, Bitbucket and others works like this.

The downside of this approach is that it requires some back-end services running that manages the indexes.

Doug Thompson

unread,
Jun 7, 2015, 1:01:49 PM6/7/15
to fireba...@googlegroups.com
Great model and thanks for the Gist.

Question - how do you query for all employees who have an "admin" position? If I understand things correctly you can't query for children on their children.

Curious how you'd approach it if you don't want to do the work client side.

Kaue Machado

unread,
Jun 7, 2015, 2:29:30 PM6/7/15
to fireba...@googlegroups.com
To be honest my app doesn't do this query, so I haven't thought about how to structure the data for this use case.

The only two concerns that I have about access level in my app are:
1) User specific feature flags: I need to show client side features based on a user access level, and since I always store the user data locally on authentication it's easy for me to get the user's access level.
2) Security validation: Only a user that has a given position in a given company must be able to write to a given node in a company node. My security rule for this in the moment is:
"private_company_profiles": {
  "$company_id": {
    ".write": "data.child('employees').child(auth.uid).val() !== null || root.child('global_access').child(auth.uid).val() === true"
  }
},

Note that I'm not yet checking for the position/access level, right now I'm only checking if the user is an employee and I'm granting access to the whole company node, but you could change the rule to be something like ' .val() == "admin" 'and grant access only to a specific company child node. Also, I have a node called "global_access" which grants access to the system admins (only me right now).

If none of the above covers your use case, you could potentially do a side back-end service that return the filtered data that you want, or maybe there is a better way to structure the data to fit your use case.

Jay

unread,
Jun 8, 2015, 12:23:44 PM6/8/15
to fireba...@googlegroups.com
Hey Kaue, thanks for the example structure. Our dataset is very similar but our users only belong to one company. 

So to address Doug's question, that allows us to use a flatter structure so admin users can be queried with the company->users node.

companies
  company_id
   
name: someName
    address: someAddress
   
Users
      u001
       user_id
: uid
       role
:   admin
      u002
       user_id
: uid
       role
:   admin


All of that being said - I would like to circle around to the original question:

How to create an initial authenticated admin user for each company without manually creating them, creating them through a dedicated server & app or encoding a 'master' user credentials into an app. The concept is the admin user ( the business owner for example) gets the app, creates their account, adds some users and then their employees (users) can then access the data.

Fireproof Socks

unread,
Jun 9, 2015, 1:51:37 PM6/9/15
to fireba...@googlegroups.com
I would love to see some more examples in the security rules for addressing this type of scenario.  I agree with Jay: seems that this would merit more discussion since those types of structures would naturally gravitate towards a service like Firebase.  My own sketches on how to handle this problem rely on setting up my own API which can make additions or edits on the Firebase data authenticating via the API secret instead of as a regular user.

Mike McDonald

unread,
Jun 14, 2015, 7:41:32 PM6/14/15
to fireba...@googlegroups.com


So Jacob was going in the right direction about needing to bootstrap this. Firebase has no concept of application adminstrator other than what the application developer creates, so there are two ways of doing this. I will say that minting a custom token would be the cleaner and more performant way of doing this, but ultimately the security rules will look similar.

To bootstrap this, we need to do the following:

1) Create a new email/password user
2) Allow them to write data to their users node
2) Allow them to claim a company by becoming the company administrator
3) Allow them to then write information to that company

Let's imagine a simple structure:

root
  users
    $userID
      role
: string
      company
: string
  companies
    $companyID
// In this example we'll just use the company name as the ID, but we should probably use a push ID here to prevent collisions
      admins
:
        $userID
: boolean
      employees:
        $userID: boolean

Sample security rules would look something like the following:

{
 
"rules": {
   
".read": false,
   
".write": false,
   
"users": {
     
"$userID": {
       
".read": "auth.uid === $userID",
       
"role": {
         
".write": "root.child('companies/' + newData.parent().child('company').val() + '/' + newData.val() + '/' + auth.uid).exists()",
         
".validate": "newData.isString()"
       
},
       
"company": {
         
".write": "root.child('companies/' + newData.val() + '/admins/' + auth.uid).val() === true || root.child('companies/' + newData.val() + '/employees/' + auth.uid).val() === true",
         
".validate": "newData.isString()"
       
}
     
}
   
},
   
"companies": {
     
"$companyID": {
        
".read": "root.child('users/' + auth.uid + '/company').val() === $companyID"
        ".write": "!data.exists() || (root.child('users/' + auth.uid + '/company').val() === $companyID && root.child('users/' + auth.uid + '/role').val() === 'admin')",
       
"admins": {
         
"$userID": {
           
".validate": "newData.isBoolean()"
         
}
       
},
       
"employees": {
         
"$userID": {
           
".validate": "newData.isBoolean()"
         
}
       
}
     
}
   
}
 
}
}

Then, clients can do the following:

var ref = new Firebase('https://your-firebase-app.firebaseio.com');
var me = null;
ref.authAnonymously(function(error, authData) { // Note that I use anonymous since it's easy--email/password works as well
 
if (error) {
    console
.log('Auth error: ' + error.message);
 
} else {
    me
= authData;
 
}
});

/* Time passes... */

ref.child('companies/initech/admins/' + me.uid).set(true); // Claim a company by creating it

ref.child('users/' + me.uid + '/company').set('initech'); // Once a company is created, set your role to is

ref.child('users/' + me.uid + '/role').set('admins'); // Once you claimed admin rights, set admin role as well

The difficult step here is that *any* authenticated user can create a company: in order to do this without a server process or hard coding this in, one has to be comfortable with this being the case. Once that company has been created, the permissions close to only an admin can write to that location, so the user who created the company must do the last two steps to create their admin role in the company. There are a few other ways to help prevent unauthorized users from being able to do this, like having email verification (we're considering how to best do this). The other option would be for you to automatically populate the $userID field when you send out an invitation (assuming you know their email address--though with email verification, you could create the account and send the verification to their email address, at which point they could pick it up and run with it).

Note that this solution is extensible so that an administrator can add an "employee" type user (say to initech/employees), and the employee can complete the last two steps (setting themselves up as an employee working for initech) without the admin having to do anything additional.

Let me know if this makes sense. This isn't a complete set of security rules for any application--it's just illustrative of a way to bootstrap up those permissions.

Thanks,
--Mike

Jay

unread,
Jun 15, 2015, 6:17:37 PM6/15/15
to fireba...@googlegroups.com
This is really good info - we are still wrapping our brain around the rules.

Question for clarification:  the rules

"users": {
     
"$userID": {
       
".read": "auth.uid === $userID",

So this would allow an authenticated user to access the users node.

If a new user creates a company, would they then be authenticated and therefore be able to read all of the data (other users id, role and company) in the users node?

Tom Larkworthy

unread,
Jun 15, 2015, 6:53:00 PM6/15/15
to fireba...@googlegroups.com
$userID is a pattern match. So the path users/joe is only readable by the person authenticated with uid === "joe" (i.e. authenticated users can read only their user profile)


--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.

Jacob Wenger

unread,
Jun 16, 2015, 11:59:32 AM6/16/15
to fireba...@googlegroups.com
Jay, you can find more details on that specific rule right here.

Mike McDonald

unread,
Jun 18, 2015, 12:06:22 PM6/18/15
to fireba...@googlegroups.com
Jay, in regards to the specific question, you could add one more rule to the ".read" clause to add what you wanted:

".read": "auth.uid === $userID || root.child('users/' + auth.uid + '/company').val() === root.child('users/' + $userID + '/company').val()"

This just checks that you both work for the same company (and you could chain other things if this is not the expected behavior, like if you only want admins to be able to view everyone's data).

Thanks,
--Mike
Reply all
Reply to author
Forward
0 new messages