So that app, it’s going to need users, right?
I’ve been working on a fun HTML5 app for a few months now, using Angular 1.x, Bootstrap, and Firebase. Angular is getting good stuff done and I have to say I’ve been extremely impressed with Firebase, but I’ll get to that in a bit.
In short: My app is like that of any other startup. A series of sprints leading toward some minimum viable product, with the occasional stumble along the way. Anything you can do to shave off a week here or there is a big win. That’s why I’m giving away the user profile management part. If you’re building on Angular and plan to use Firebase, I’ve already done a lot of the work for you. Or at least I can show you the general outlines of what you’ll need to build your own.
After doing a spike on the main functionality for proof-of-concept, the next order of business was to build a user profile management module. And since, as a user, I personally don’t like having to create a username and password for every new site I want to interact with, I’m partial to those who allow me to use social sign-in via OAuth. That way, I only have to place my authentication trust in a few big players, and other sites can confirm my identity via those players we both trust.
Fortunately, in addition to standard password-based authentication, Firebase integrates with Google, Facebook, Twitter, and GitHub out of the box, and it’s a breeze to set them up to work with each other. Other options include anonymous login (guest accounts) and custom authentication using secure JSON Web Tokens.
I chose to implement sign-in via password and the four social providers. And while the process was relatively straightforward, it was a week or so of necessary drudgery. Maybe it will save someone else a few days. That’d be nice.
Fig 1: Firebase’s flexible authentication options.
A Few Words About Firebase
Totally freakin’ awesome. In addition to providing app hosting from a fast CDN, Firebase is a queryable, indexable JSON database, with powerful rules-based security that can be customized down to every node. In this case, we’ve got a “users” node, which contains a node for each user, where the key is a globally unique identifier assigned by Firebase. For password-based users, this is just a gobbledygook string. For social network users, it’s the OAuth provider name (e.g., ‘facebook’) followed by the user’s id on the provider’s network.
Fig 2: The data structure for our basic user profile.
The Public and Private Bits of the Profile
Even though we probably want to keep the user’s email address hidden from other users, we would want to expose some things, like display name and bio. That’s handled with specific security rules, which you manage in the Firebase dashboard for your project.
The key to understanding Firebase security rules is that read and write operations are disallowed at every node unless access is granted at or above that node. In other words, access is inherited by child nodes, and can’t be revoked, only granted.
In the demo, the user is the only one (other than the site admin) who can read and write to all of their profile data. Note, however, that there is a node called “expose” containing several items (name, bio, image, and achievements) that we expose to other authenticated users on a read-only basis.
Also, we use a validation rule to ensure that new items written to the database under the “users” node must have a child called “uid” AND its value must be the same as the key the profile is stored under. This way, when we create records in other parts of the database and key them to this user, we’re certain that the user id will always be correct on the profile we retrieve.
Fig 3: The Firebase security rules for our database.
How Do I Know The Rules Work?
A nice feature of the Firebase dashboard is the simulator. It allows the admin to fake authentication as any user (or none at all) and then attempt accessing any node. It will tell you explicitly why it allows or disallows any read or write you attempt.
Fig 4: An authenticated user is denied access to another’s profile.
Fig 5: An authenticated user is allowed access to another’s “expose” node.
Think Flat, and Don’t Sweat Making Calls
We could continue putting lots of collections of things, each with their own controlled-access rules down to a depth of 32 levels. That way, you could fetch every piece of a user’s data (invoices, messages, etc.) by retrieving their profile node.
But the rule of thumb is to think flat. Don’t build deep structures, make shallow ones and link them together with keys. Treat it like any relational database. Make an invoices node, and put all the invoices there. You can index invoices on uid, and fetch all of them for a given user with a simple query.
But that means making a lot of calls if you have a lot of data, right? Potentially, yes. But, Firebase uses websockets, so each call doesn’t carry the ordinary overhead of an HTTP request negotiation. It comes down to just the bytes required to specify the requested data and the data itself, returned on the already open pipe. HTTP chattiness is minimized, and therefore, the desire to fetch big blocks of data all at once (as opposed to just what you need, when you need it) is too.
In short, Firebase is fast, fun, flexible, and highly recommended.
Fig 6: The live demo, hosted on Firebase's CDN
Try Out the Demo and Download the Code
Aside from user authentication, the demo implements basic navigation and a mobile-first design. The code is open source under the BSD 3-Clause License.