OAuth is, to borrow a phrase, a bag of hurt. Not just any bag, but the kind that is thrown over your head right before you’re thrown into a van and driven down to Tijuana and ransomed for $50,000. I don’t like OAuth.
Sometimes, though you need to use it. I’m working on an internal tool at work and want to make sure that only co-workers can use it. I could do the username/password thing, but the world has enough of those. We use Google docs for the office, so everyone has a Google account that could, theoretically, be used to sign in. Thus, OAuth.
The tool itself is a web app that uses Ember on the front-end and Hapi and MongoDB as the API and database. It’s my first time using Hapi, and I like it quite a bit, seems like a really good tool for API building.
Since this is a completely separate client and server, it adds a bit of a wrinkle to the OAuth dance. There are plenty of “Sign in with X” libraries out there, but they mostly seem to be written with the intent that the user will only be interacting with that service. In other words, if I was building a Google Docs client app, my life would be easier, but no, I am only authenticating a user’s account and then treating that as the key to my own system… which makes it more manual. It also doesn’t help that Google’s OAuth documentation is lousy. There are many different ways to authenticate, and multiple libraries to support the different ways, and they’ve changed over time, so searching for help is difficult.
Here’s how it’s supposed to work:
- The client (Ember) “signs in with Google”. If this is successful it receives an
authorizationCode. This code by itself is useless, it just means the user signed in ok.
- The server (Hapi) needs to validate this code and turn it into an
- If that is successful, we can query the Google API (from the server still) for the account information, verify that they are from Tallwave, and then send credentials back down to the client.
- The client receives the credentials, stores them locally, and includes them for subsequent API requests.
Errors need to be handled at all the steps along the way too.
Step 1 — Client Side
Torii is a pretty good (though a wee bit out of date) library for Ember that handles a good amount of the Google sign in process. Follow its installation instructions and setup the configuration and the proper adapter. Here’s my
1 2 3 4 5 6 7 8 9 10 11
scope property is important. That allows you to query for the user’s account later on. The only other point with Torii worth mentioning is how I sent the authorization code to my API. The
open method on the
torii-adapters/application.js is called after Google auth finishes, and that is where you create the “session” for authentication.
By default Ember uses the JSON API for the API layer, and I tend to go with the flow. But in this case, since we’re sending a single value to the server and will throw it away immediately after… Setting up a model seems overkill for that. We can drop down to regular old jQuery for this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Worth pointing out is that we are expecting our API to return some JSON that will be:
- Stored locally (
sessionStoreis a service, which we’ll define later), and will be retrieved later.
- Returning the
currentUserobject will merge it into the underlying session, so that it can be retrieved in routes, components, templates etc. This isn’t permanent storage though, if the user refreshes the page, it’ll be gone, hence
Step 2 — On the Server
This is where the fun begins. If you’re playing at home, I used Node 5 and Hapi 13.x.x. First, you’ll need to get your credentials from the Google Developer Console. I’m not going to walk through those steps, but you’ll need an OAuth client ID for a Web Application. Specifically, you’ll need the client id, client secret, and redirect URL (yes, even though you won’t be redirecting to anything at this point).
Then, install the
Now, let’s build a service that sends Google an
authorizationCode and gets an accessToken back. You’ll call this service from your route handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
That’s kind of a mouthful and could definitely stand a good refactoring, but it goes through the steps needed to get some information from Google. You can see more APIs you can call, but the documentation on a lot of them is rather sparse. For example, adding the
params is not apparent, but necessary for each call.
We’ve now determined if the user should actually have access to what they just signed in to and sent the response back to the client. That’s pretty good! Refresh your beverage, take a deep breath and then let’s get handle that response and get our client in order to keep moving.
Step 3 — Back on the Client
You might have noticed again that we did not send the session information back in JSON API format either. This was intentional since we aren’t going to be storing this in the standard Ember store, and therefore don’t need the additional benefit of using something that standardized. You could if you wanted to, but for this occasion, I did not want to set up a model (or in this case models to support the nested structure).
Back up in our
Ember.RSVP.Promise handler there was this line:
Once you receive a response, you’ll need to store it. You could use Ember Data, but that felt overkill to me; I needed to access the tokens within the response for subsequent API calls, and nothing else. I ended up building an Ember service that serialized the content and stored it in localStorage. You can go your own way too.
Step 4 — Making authenticated requests
We’re authorized (or have displayed a fancy error message) and are ready to make actual requests to get actual data. I like to authenticate my API requests by including my authorization token in the
Authorization header on requests. Ember allows for this by overriding the
headers computed property in your Adapter:
1 2 3 4 5 6 7 8 9 10
Step 5 — Verify authentication on the server
There are may ways to authenticate requests with Hapi. Hapi calls these “Schemes”. A Scheme is made up of multiple Strategies but since we only have one, we’ll just refer to it as a Scheme.
You define a Scheme as a function that takes a
server and some
options as arguments and go from there. Here’s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Now that we’ve defined our Scheme, we need to register it with Hapi. Wherever the server is set up add this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
If you do use a default auth strategy, you can override that on individual routes in their handler config:
1 2 3 4 5 6 7 8
Or use the boolean
false to skip auth altogether for a route, such as your session creation API.
And we’re done! You now have an authenticated API. There are plenty of other points that could be expanded on, but it’s getting a little long in the tooth now.
Wait, why not use Bell?
The Hapi organization maintains a project called Bell that is a “Third-party login plugin for hapi”, including OAuth services. It’s great, but the catch is that since half of our authentication takes place in the browser, we can’t quite use it the way it is intended to be used.