Ember Simple Auth vs. Torii: OAuth2 Implicit Grant in Your Ember App

If you're adding OAuth2 login to your Ember app, some quick Googling will turn up two well-maintained addons: Ember Simple Auth (ESA) and Torii. ESA bills itself as "a lightweight library for implementing authentication/authorization" while Torii is "a set of clean abstractions for authentication". There is a lot of overlap in those headlines, so it's not immediately clear which library will be a better fit for your use case.

This is the first of two posts about ESA and Torii with a focus on using them with the OAuth2 implicit grant flow. This post aims to clarify which problems each library solves so you can make the right choice for your application. The next post will be a deep dive into the problem of how to unobtrusively refresh your user's access token.

A Disclaimer on Open Source Entitlement

Nothing ticks me off like people complaining about open source projects because they don't fit some narrow use case. I am grateful for anyone who takes the time to publish and document an Ember addon or any open source project. With that in mind, I want to emphasize that this post is not a judgment on the quality of either library, but just fitness for my particular use. If I sneak in something that sounds like a value judgment, like "this library is better because..." please mentally insert "this library is better for my specific use case because...". Ember Simple Auth and Torii are both great libraries; hats off to their respective maintainers.

So, let's get on with it.

Competing or Complementary?

From a cursory glance at both projects, it's not clear if they solve overlapping problems or complement each other. In this article the author of ESA, Marco Otte-Witte, provides a high-level distinction:

Simple Auth is more about maintaining session/session events, providing a framework for authenticating a strategy, and authorizing requests. Torii is more about interfacing with these external authentications.

The ESA readme reinforces this idea. You'll notice that one of the prepackaged authenticators is Torii. To me, this suggested that ESA provides an authentication "framework" - tools to manage all the ways auth touches your app - while Torii is a more specialized library that implements the OAuth2 dance for social login providers.

The Torii readme muddies this view. Features like a session service and router DSL seem to make it framework-ish after all; it also provides tools that can manage and react to session state throughout your app. So both libraries operate at a similar level of abstraction, yet one can plug into the other? To understand this, I think it helps to break out common auth* tasks into 3 buckets. If your app has login, it will probably need to

  1. Authenticate via one or more external services
  2. Observe the user's session throughout the app
  3. Restrict access to routes depending on session state

Now let's compare how each library approaches these tasks.

Pluggable Authentication

Maybe your application needs to provide login via some mixture of Facebook Connect, Google Sign-In, and a username + password form. Both libraries provide an abstraction to implement different forms of authentication. ESA calls them authenticators and Torii calls them providers. For definition's sake, I will call them both authenticators.

Torii is more oriented to social login, so it comes with preconfigured OAuth2 authenticators (including GitHub, LinkedIn, Google, Facebook) and a nice base class for you to implement your own OAuth2 implicit grant authenticator. ESA is not as oriented towards social login. It has a wider range of base authenticators, but its implicit grant authenticator is not as robust as Torii's. Crucially, Torii handles creating a popup or iframe for the user to authenticate with the external OAuth2 service. The built-in ESA OAuth2ImplicitGrantAuthenticator only implements what happens after the OAuth2 service redirects to your app. I dig deeper into the implications of this in a follow-up post.

The tl;dr is that if you're using ESA, you will either need to implement some nontrivial logic on top of OAuth2ImplicitGrantAuthenticator to achieve a smooth user experience or use ToriiAuthenticator which lets you take advantage of Torii's excellent popup implementation. The downside of using ToriiAuthenticator alongside ESA is that you are now integrating two fairly complex auth libraries and requiring your team to understand their nuances.

Session Service

After your user has logged in with whatever authenticator you've chosen (or written), your application now needs a way to make use of session state throughout your app. Both libraries provide a session service for this. ESA does so by default, Torii on request. A session service provides the following abilities:

  • Give your view code (components, controllers) a central place to look for session state so they can make decisions like whether to display "Sign In" or "Sign Out" in a nav bar.
  • Provide methods to initiate logging in or logging out using your authenticators.
  • Persist session state so that a user stays logged in if they open your app in a different tab or refresh the page.

That last bit on persisting session state is tricky and, IMO, where ESA really shines. Torii is agnostic as to whether you should implement this. It's readme helpfully notes that when implementing an authenticator, you may wish to persist session state to a cookie or localStorage on success.

ESA is more opinionated here, and provides a "Session Store" abstraction for this purpose. I found it extremely useful that my session state was automagically persisted with the storage strategy of my choice (localStorage) and that if my user has my application open in multiple tabs, logging out in one tab will cause them to be immediately logged out in other tabs.

Routing

Given a global session service, you'll probably want to use it to restrict access to different routes in your application. This is such a common task that both libraries provide helpers for it.

Torii extends Ember's router DSL (your router.js) to let you define which routes should only be accessible to a logged-in user. Here's the example from their readme:

Router.map(function(){  
    this.authenticatedRoute('my-account');
    this.route('login');
});

This is slick. Configuration of which routes are restricted and which are open live in one file. If a user who is not logged in tries to access an authenticated route, Torii will try to fetch session information and send the user back to the login page if unsuccessful.

ESA lets you implement similar behavior by providing mixins for authenticated and unauthenticated routes. While this isn't as nifty as extending the router DSL, it has worked fine for me in practice.

Verdict

Unsurprisingly, each library has its respective strengths. Using Torii just for its popup authentication and ESA for its session management is a solid choice - I've done that for an app I put into production and the Ghost team does that for their admin app.

Still, it feels suboptimal to include both in my application. If you look at the Ghost repo, you'll see 4 top-level directories for auth* in their app: authenticators, authorizers, and session-stores come from ESA, while torii-providers is for Torii. A new developer who comes on to that codebase will need to scour the documentation for both ESA and Torii to understand how the app is handling auth*. In a follow-up post, I dig a little deeper into implementing silent authentication and discuss implementation strategies that combine ESA and Torii and ESA alone.