(better) Authentication in ember.js

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

Contact us!

I’m using the latest (as of early August 2013) ember.js and handlebars releases in this example.

**Update:**I changed the section on actually using the token to use $.ajaxPrefilter instead of a custom ember-data adapter

anchorThe basics

The basic approach is still the same as in our initial implementation - we have a /session route in our Rails app that the client POSTs its credentials to and if those are valid gets back an authentication token together with an id that identifies the user’s account on the server side.

This data is stored in a "session" object on the client side (while technically there is no session in this stateless authentication mechanism, I still call it session in absence of an idea for a better name). The authentication token is then sent in a header with every request the client makes.

anchorThe client "session"

The "session" object on the client side is a plain Ember.Object that simply keeps the data that is received from the server on session creation. It also stores the authentication token and the user’s account ID in cookies so the user doesn’t have to login again after a page reload (As Ed points out in a comment on the old post it’s a security issue to store the authentication token in a cookie without the user’s permission - I think using a session cookie like I do should be ok as it’s deleted when the browser window is closed. Of course you could also use sth. like localStorage like Marc points out. I’m creating this object in an initializer so I can be sure it exists (of course it might be empty) when the application starts.


Ember.Application.initializer({
  name: 'session',

  initialize: function(container, application) {
    App.Session = Ember.Object.extend({
      init: function() {
        this._super();
        this.set('authToken', $.cookie('auth_token'));
        this.set('authAccountId', $.cookie('auth_account'));
      },

      authTokenChanged: function() {
        $.cookie('auth_token', this.get('authToken'));
      }.observes('authToken'),

      authAccountIdChanged: function() {
        var authAccountId = this.get('authAccountId');
        $.cookie('auth_account', authAccountId);
        if (!Ember.isEmpty(authAccountId)) {
          this.set('authAccount', App.Account.find(authAccountId));
        }
      }.observes('authAccountId')
    }).create();
  }
});

When this has run I can always access the current "session" information as App.Session. Notice the .create() at the end of the initializer that creates an instance of the Ember.Object right away. When we need to check whether a user is authenticated we can simply check for presence of the authToken property. Of course we could add a isAuthenticated() method that could perform additional checks but we didn’t have the need for that yet.

This "session" object will also load the actual account record from the server if the authAccountId is set (this.set('authAccount', App.Account.find(authAccountId));. This allows us to e.g. use App.Session.authAccount.fullName in our templates to display the user’s name or similar data.)

To actually use the authToken when making server requests, we register an AJAX prefilter that adds the authentication token in a header as long as the request is sent to our domain:


Ember.$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  if (!jqXHR.crossDomain) {
    jqXHR.setRequestHeader(
      'X-AUTHENTICATION-TOKEN',
      App.Session.get('authToken'),
    );
  }
});

The Rails server can then find the authenticated user by the token in the header:

class ApplicationController < ActionController::Base

  def current_user
    @current_user ||= begin
      auth_token = request.env['HTTP_X_AUTHENTICATION_TOKEN']
      Account.find_by(auth_token: auth_token) if !!auth_token
    end
  end

end

anchorLogging in

As described above, the login API is a simple /session route on the server side that accepts the user’s login and password and responds with either HTTP status 401 when the credentials are invalid or a session JSON when the authentication was successful. On the client side we have routes for creating and destroying the session:


App.Router.map(function() {
  this.resource('session', function() {
    this.route('new');
    this.route('destroy');
  });
});

The SessionNewController only needs one action login that sends the entered credentials and acts according to the server’s response - if the server responds successfully it reads the session data from the response and updates the App.Session object accordingly. It also checks whether there is an attempted transition that was intercepted due to missing authentication and retries that if it exists (This is the case where the user tries to access a certain page without having authenticated, is redirected to the login form, logs in and is redirected again to the initially requested page).


App.SessionNewController = Ember.Controller.extend({
  login: function() {
    var self = this;
    var data = this.getProperties('loginOrEmail', 'password');
    if (!Ember.isEmpty(data.loginOrEmail) && !Ember.isEmpty(data.password)) {
      var postData = { session: { login_or_email: data.loginOrEmail, password: data.password } };
      $.post('/session', postData).done(function(response) {
        var sessionData = response.session || {};
        App.Session.setProperties({
          authToken: sessionData.auth_token,
          authAccountId: sessionData.account_id
        });
        var attemptedTransition = App.Session.get('attemptedTransition');
        if (attemptedTransition) {
          attemptedTransition.retry();
          App.Session.set('attemptedTransition', null);
        } else {
          self.transitionToRoute('games');
        }
      });
    }
  }
});

Notice that we do not handle the error case here. To have a better user experience you would probably want to define a .fail handler as well that display an error message.

The template is just a simple form (actual elements, classes etc. of course depend on your specific application):


<form {{action login on="submit"}}>
  {{view
    Ember.TextField
    valueBinding="loginOrEmail"
    placeholder="Login or Email"
  }}
  {{view
    Ember.TextField
    valueBinding="password"
    type="password"
    placeholder="Password"
  }}
  <button class="btn">Login</button>
</form>

anchorLogging out

Logging out is actually pretty simple as well. The client just sends a DELETE to the same /session route that makes the server reset the authentication token in the database so that the token on the client side is invalidated. The client also deletes the saved session information in App.Session so there’s no stale data.


App.SessionDestroyController = Ember.Controller.extend({
  logout: function() {
    var self = this;
    $.ajax({
      url: '/session',
      type: 'DELETE'
    }).always(function(response) {
      App.Session.setProperties({
        authToken: '',
        authAccountId: ''
      });
      self.transitionToRoute('session.new');
    });
  }
});

As this action should be triggered as soon as the user enters the /#/session/destroy route, we have a simple route implementation that triggers the action upon route activation:


App.SessionDestroyRoute = Ember.Route.extend({
  renderTemplate: function(controller, model) {
    controller.logout();
  }
});

anchorAuthenticated routes

To easily enable authentication for any route in the application, I created an App.AuthenticatedRoute that extends Ember.Route and that all routes that need to enforce user authentication can extend again:


App.AuthenticatedRoute = Ember.Route.extend({
  redirectToLogin: function(transition) {
    App.Session.set('attemptedTransition', transition);
    this.transitionTo('session.new');
  },

  beforeModel: function(transition) {
    if (!App.Session.get('authToken')) {
      this.redirectToLogin(transition);
    }
  }
});

Notice that redirectToLogin sets the attemptedTransition of App.Session so that the user will be redirected to the initially requested page after successfulyl logging in.

This is better authentication with ember.js - enjoy!

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

Contact us!

Grow your business with us

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