Authentication in ember.js

anchorIf you're facing challenges with Ember.js and need a helping hand, reach out!

Contact us!

When we started our first project with ember.js, the first thing we came across was how to implement authentication. While all of us had implemented authentication in "normal" Rails apps several times we initially weren’t sure how to do it in ember.js. Also information on the internet was scarce and hard to find.

The only more elaborate sample project I found was the ember-auth plugin. While that seemed to be very complete and high quality it is also very heavy weight and I didn’t want to add such a big thing to our codebase only to implement simple authentication into our app. So I rolled my own implementation.

anchorThe basics

The general route to go with authentication in ember.js is to use token based authentication where the client submits the regular username/password credentials to the server once and if those are valid receives an authentication token in response. That token is then sent along with every request the client makes to the server. Having understood this the first thing to do is to implement a regular login form with username and password fields:


<form>
  <label for="loginOrEmail">Login or Email</label>
  {{view
    Ember.TextField
    valueBinding="loginOrEmail"
    placeholder="Login or Email"
  }}
  <label for="password">Password</label>
  {{view Ember.TextField valueBinding="password" placeholder="Password"}}
  <button {{action "createSession"}}>Login</button>
</form>

That template is backed by a route that handles the submission event and posts the data to the /session route on the server - which then responds with either status 401 or 200 and a JSON containing the authentication token and the id of the authenticated user:


App.SessionsNewRoute = Ember.Route.extend({
  events: {
    createSession: function() {
      var router = this;
      var loginOrEmail = this.controller.get('loginOrEmail');
      var password = this.controller.get('password');
      if (!Ember.isEmpty(loginOrEmail) && !Ember.isEmpty(password)) {
        $.post('/session', {
          session: { login_or_email: loginOrEmail, password: password },
        },
        function(data) {
          var authToken = data.session.auth_token;
          App.Store.authToken = authToken;
          App.Auth = Ember.Object.create({
            authToken: data.session.auth_token,
            accountId: data.session.account_id,
          });
          router.transitionTo('index');
        });
      }
    },
  },
});

I’m using a route instead of a controller here as redirecting should only be done from routes and not controllers. See e.g. this SO post for more info.

The response JSON from the server would look somehow like this in the successful login case:

on
{
  "session": {
    "auth_token": "<SOME RANDOM AUTH TOKEN>",
    "account_id": "<ID OF AUTHENTICATED USER>"
  }
}

At this point the client has the authentication data necessary to authenticate itself against the server. As tat authentication data would be lost every time the application on the client reloads and we don’t want to force a new login every time the user reloads the page we can simply store that data in a cookie (of course you could use local storage etc.):


$.cookie('auth_token', App.Auth.get('authToken'));
$.cookie('auth_account', App.Auth.get('accountId'));

anchorMaking authenticated requests

The next step is to actually send the authentication token to the server. As the only point point of interaction between client and server in an ember.js app is when the store adapter reads or writes data, the token has to be integrated in that adapter somehow. As there’s not (yet) any out-off-the-box support for authentication in the DS.RESTAdapter, I simply added it myself:


App.AuthenticatedRESTAdapter = DS.RESTAdapter.extend({
  ajax: function (url, type, hash) {
    hash = hash || {};
    hash.headers = hash.headers || {};
    hash.headers['X-AUTHENTICATION-TOKEN'] = this.authToken;
    return this._super(url, type, hash);
  },
});

Now the adapter will pass along the authentication token with every request to the server. One thing that should be made sure though is that whenever the adapter sees a 401 response which would mean that for some reason the authentication token became invalid, the session data on the client is deleted and we require a fresh login:


DS.rejectionHandler = function (reason) {
  if (reason.status === 401) {
    App.Auth.destroy();
  }
  throw reason;
};

anchorEnforcing authentication on the client

Now that the general authentication mechanism is in place it would be cool to have a way of enforcing authentication on the client for specific routes so the user never gets to see any pages that they aren’t allowed to. This can be done by simply introducing a custom route class that will check for the presence of a session and if none is present redirects to the login screen. Any other routes that require authentication can then inherit from that one instead if the regular Ember.Route


App.AuthenticatedRoute = Ember.Route.extend({
  enter: function () {
    if (
      !Ember.isEmpty(App.Auth.get('authToken')) &&
      !Ember.isEmpty(App.Auth.get('accountId'))
    ) {
      this.transitionTo('sessions.new');
    }
  },
});

This is actually very similar to the concept of an AuthController in Rails with a before_filter that enforces authentication:

class AuthenticatedController < ApplicationController

  ensure_authenticated_user

end

anchorCleanup

As the code is now spread up into a number of files and classes, I added a Session model:


App.Session = DS.Model.extend({
  authToken: DS.attr('string'),
  account: DS.belongsTo('App.Account'),
});

alongside an App.AuthManager accompanied by a custom initializer to clean it up:


App.AuthManager = Ember.Object.extend({
  init: function() {
    this._super();
    var authToken = $.cookie('auth_token');
    var authAccountId = $.cookie('auth_account');
    if (!Ember.isEmpty(authToken) && !Ember.isEmpty(authAccountId)) {
      this.authenticate(authToken, authAccountId);
    }
  },

  isAuthenticated: function() {
    return !Ember.isEmpty(this.get('session.authToken')) && !Ember.isEmpty(this.get('session.account'));
  },

  authenticate: function(authToken, accountId) {
    var account = App.Account.find(accountId);
    this.set('session', App.Session.createRecord({
      authToken: authToken,
      account: account,
    }));
  },

  reset: function() {
    this.set('session', null);
  },

  sessionObserver: function() {
    App.Store.authToken = this.get('session.authToken');
    if (Ember.isEmpty(this.get('session'))) {
      $.removeCookie('auth_token');
      $.removeCookie('auth_account');
    } else {
      $.cookie('auth_token', this.get('session.authToken'));
      $.cookie('auth_account', this.get('session.account.id'));
    }
  }.observes('session'),
});

This is simple authentication with ember.js!

anchorIf you're facing challenges with Ember.js and need a helping hand, reach out!

Contact us!

Stay up to date on Ember

Subscribe to our newsletter and stay up to date about the latest events, workshops, and other news around Ember.

Team up with us to go further!

Our experts are ready to guide you through your next big move. Let us know how we can help.
Get in touch