Real-world Authentication with Ember Simple Auth

Getting authentication up and running in Ember can be overwhelming.

Now that we understand how Services and token-based authentication work, we are ready to get started with a simple –yet powerful– framework called Ember Simple Auth.

In this episode, we will enhance our existing “Secretcodez” application.

Good news: since we essentially created a dumbed-down version of Ember Simple Auth (using the same concepts), the upgrade will be very smooth.

Using Ember Simple Auth

Let’s begin by installing the add-on:

$ ember install ember-simple-auth

That’s it. We are ready to tackle the upgrade of our sample app!

If you haven’t gone through part 1 but would still like to follow this guide, make sure you get the code:

$ git clone https://github.com/frank06/secretcodez.git
$ git checkout master

Some files listed below will be modified substantially. Most of the time you can copy that code onto your own app, replacing previous content.

Secretcodez v2

To recap: Secretcodez is a silly app that displays nuclear missile activation codes only to logged in users. It requests an OAuth2 token from a small embedded web server in order to show the codes.

The core concept in the authentication mechanism is the Ember Service. In Secretcodez we called it authManager. Ember Simple Auth (ESA) has its own service, named session. We are going drop ours and use ESA’s – but we’ll keep the authManager variable name.

Bust the service!

$ rm app/services/auth-manager.js

Meanwhile, in the login page component…

// app/components/login-page.js

export default Ember.Component.extend({

  authManager: Ember.inject.service('session'),

  actions: {
    authenticate() {
      const { login, password } = this.getProperties('login', 'password');
      this.get('authManager').authenticate('authenticator:oauth2', login, password).then(() => {
        alert('Success! Click the top link!');
      }, (err) => {
        alert('Error obtaining token: ' + err.responseText);
      });
    }
  }

});

(Note that we are now passing in an Authenticator, authenticator:oauth2.)

An Authenticator is defined by ESA as:

The authenticator authenticates the session. The actual mechanism used to do this might e.g. be posting a set of credentials to a server and in exchange retrieving an access token, initiating authentication against an external provider like Facebook etc. and depends on the specific authenticator.

Ember best practices delivered straight to your inbox? Tell me where:

(One e-mail every month. No BS. Unsubscribe anytime!)

So let’s create our OAuth2 authenticator:

$ mkdir app/authenticators
$ touch app/authenticators/oauth2.js

With the following content:

// app/authenticators/oauth2.js

import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';

export default OAuth2PasswordGrant.extend();

This strategy effectively replaces our Ember.$.ajax call to fetch the token at /token! All the heavy work is now done by Ember Simple Auth!

If you needed to override the token endpoint, here’s how:

// app/authenticators/oauth2.js

export default OAuth2PasswordGrant.extend({
  serverTokenEndpoint: "/path/to/token"
});

At the moment, if no token is available when the secret route is accessed, a 401 Unauthorized error will be thrown (you can probably notice it in your console). This will happen during the findAll call to the backend.

By mixing in AuthenticatedRouteMixin we get that check for free:

// app/routes/secret.js

import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  model() {
    return this.store.findAll('code');
  }
});

The application route was used to catch those errors and transition to the login route. With ESA, we simply mix in ApplicationRouteMixin and it will be handled for us.

// app/routes/application.js

import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

export default Ember.Route.extend(ApplicationRouteMixin);

Finally, the application adapter had the authManager injected and sent an Authorization header. Again, ESA takes care of this for us:

// app/adapter/application.js

import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default DS.RESTAdapter.extend(DataAdapterMixin, {
  namespace: 'api',
  authorizer: 'authorizer:application'
});

Which brings us to Authorizers. What are they?

Authorizers use the session data aqcuired by an authenticator when authenticating the session to construct authrorization data that can e.g. be injected into outgoing network requests etc. Depending on the authorization mechanism the authorizer implements, that authorization data might be an HTTP header, query string parameters, a cookie etc.

In order to replace our Authorization: Bearer "some token" header, we will leverage ESA’s OAuth2Bearer authorizer. Let’s create:

$ mkdir app/authorizers
$ touch app/authorizers/application.js

With…

// app/authorizers/application.js

import OAuth2Bearer from 'ember-simple-auth/authorizers/oauth2-bearer';

export default OAuth2Bearer.extend();

That’s it for the upgrade!

If we restart ember server and check it out… the application behaves exactly the same! (Except it’s much more solid.) We are now delegating a great deal of complexity to Ember Simple Auth!

Set the current user

Those of us familiar with Devise (the Rails authentication solution) will recall the current_user variable available to Rails’ controllers.

We know a user has authenticated when the isAuthenticated property becomes true. The plan is to fetch the current user whenever that happens.

Let’s whip up a custom session service (as well as a User model to represent our user):

$ ember g service session
$ ember g model user email:string

We will extend ESA’s Session service and add a (computed) property called currentUser:

// app/services/session.js

import DS from 'ember-data';
import ESASession from "ember-simple-auth/services/session";

export default ESASession.extend({

  store: Ember.inject.service(),

  currentUser: Ember.computed('isAuthenticated', function() {
    if (this.get('isAuthenticated')) {
      const promise = this.get('store').queryRecord('user', {})
      return DS.PromiseObject.create({ promise: promise })
    }
  })

});

Since querying the backend for a user involves a promise, we return a PromiseObject that will update our template when the promise resolves.

Neat! But… hold your horses! It’s not as if we had an API endpoint for the current logged in user.

Typically this response depends on a cookie and a DB lookup. But let’s quickly create a dummy response, which for now will live at /api/users.

// server/index.js

// ...

app.get('/api/users', function (req, res) {
  return res.status(200).send({ user: { id: 1, email: 'vladimir@kremlin.ru' }});
});

// ...

It’s high time to make use of the feature! We will prepend a snippet to the secret page:

{{! app/templates/components/secret-page.hbs }}

{{#if authManager.currentUser}}
  Logged in as {{authManager.currentUser.email}}
{{/if}}

<h1>OMG DA CODEZ!!</h1>

<ul>
{{#each model as |code|}}
  <li><strong>{{code.description}}</strong></li>
{{/each}}
</ul>

Naturally –you guessed it– we need to inject the service into our secret page component:

// app/components/secret-page.js

export default Ember.Component.extend({
  authManager: Ember.inject.service('session')
});

Loading our app, this is what we see:

Great, it works!

Wrapping up

We have covered a fair amount of ground regarding authentication. I hope this has been useful enough for you to apply on your projects!

Remember the source code is on Github:

For further information and docs, do head over to the Ember Simple Auth website.

A future installment may bring authorization with external OAuth2 providers.

Did this guide help you? Were you able to run the sample app? Let me know in the comments!

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)