Ember Ajax and Ember Simple Auth

Ember Simple Auth (ESA) makes it really easy to implement authentication and authorisation in your apps. One of the first things you will do when setting it up is add ESAs DataAdapterMixin to your application route.js.

// app/adapters/application.js
import DS from 'ember-data';
import ENV from 'connected-insight/config/environment';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
  host: ENV.serverHost,
  authorizer: 'authorizer:token'
});

What does this do though? By adding this mixin, it means that when you use Ember Data to communicate with your API (for example this.store(...)), ESA will automatically inject the authorisation information (for example a JWT token) into outgoing request headers. Furthermore, it will also automatically invalidate the session and “log you out” if your API returns a 401 “unauthorised” responses.

But what if you aren’t using Ember Data? What if you need to make authenticated AJAX calls to your API outside of the data store? Well, the easiest way to do this is to use ember-ajax. But there is a problem: you won’t get any of the free authorisation handling that using the DataAdapterMixin gives you, so you have to reimplement it.

Thankfully, the documentation for Ember Ajax is really good, and it includes most of the parts you need to get running, but it doesn’t quite tie it all together in a single service that you can copy and paste, so I’m going to do it for you.

Ajax Service

Ember Ajax is centered around a service that you can use in your routes, controllers and components. This service has a request(...) method that you use to make AJAX calls to your API:

import Ember from 'ember';

export default Ember.Controller.extend({
  ajax: Ember.inject.service(),
  actions: {
    sendRequest() {
      return this.get('ajax').request('/posts', {
        method: 'POST',
        data: {
          foo: 'bar'
        }
      });
    }
  }
});

So to get started, let’s create our own “api” service that inherits this functionality:

> ember g service api
// services/api.js
import AjaxService from 'ember-ajax/services/ajax';

export default AjaxService.extend(AuthenticatedAjaxMixin, {
});

Let’s customise if for our application:

// services/api.js
import AjaxService from 'ember-ajax/services/ajax';
import ENV from 'connected-insight/config/environment';
import {inject as service} from '@ember/service';

export default AjaxService.extend(AuthenticatedAjaxMixin, {
  session: service(),
  host: ENV.serverHost,
  authorizer: 'authorizer:token'
});

We’ve injected the session service from ESA, we’ve added the host for our API so that Ember Ajax knows where to direct our requests (you need to make sure you have ENV['serverHost']=http://blahblah.blah in your environment.js) and we’ve defined what authorisor we’re using (I’m using ember-simple-auth-token).

Now let use the headers() function provided by Ember Ajax to inject our token. Again, this is all pretty much already in the documentation:

// services/api.js
import AjaxService from 'ember-ajax/services/ajax';
import ENV from 'connected-insight/config/environment';
import {inject as service} from '@ember/service';
import {computed} from '@ember/object';

export default AjaxService.extend(AuthenticatedAjaxMixin, {
  session: service(),
  host: ENV.serverHost,
  authorizer: 'authorizer:token',
  headers: computed('session.authToken', {
    get() {
      const authorizer = this.get('authorizer');
      let headers = {};
      this.get('session').authorize(authorizer, (headerName, headerValue) => {
        headers[headerName] = headerValue;
      });
      return headers;
    }
  }),
});

Now, we should be able to make authenticated requests to our api with:

  // ...
  api: service(),
  something() {
    this.get('api').request('/blah/blah');
  }

Invalidating on 401

But about the response? If we get a 401 from our API, we’d expect the app to log us out. But it won’t! We need to add this ourselves. If you look at the code for the DataAdapterMixin you’ll see that it’s quite straight forward.

  //...
  ensureResponseAuthorized(status/* ,headers, payload, requestData */) {
    if (status === 401 && this.get('session.isAuthenticated')) {
      this.get('session').invalidate();
    }
  }

It simply checks for a 401 and calls the session’s invalidate method. What’s even better is that if we check out the docs for ember-ajax, we’ll see that it already has similar error checking available out of the box.

So let’s overwrite the request method that Ember Ajax provides us with:

// services/api.js
import AjaxService from 'ember-ajax/services/ajax';
import ENV from 'connected-insight/config/environment';
import {inject as service} from '@ember/service';
import {computed} from '@ember/object';

export default AjaxService.extend(AuthenticatedAjaxMixin, {
  session: service(),
  host: ENV.serverHost,
  authorizer: 'authorizer:token',
  headers: computed('session.authToken', {
    get() {
      const authorizer = this.get('authorizer');
      let headers = {};
      this.get('session').authorize(authorizer, (headerName, headerValue) => {
        headers[headerName] = headerValue;
      });
      return headers;
    }
  }),
  request() {
    return this._super(...arguments).catch(error => {
      if (isUnauthorizedError(error)) {
        this.get('session').invalidate();
      }

      throw error;
    });
  }
});

So now when we get a 401 from any Ember Ajax calls to our API, it’s going to automatically invalidate our session and redirect us to our login page (bare in mind this is configured via the authenticationRoute in your ENV['ember-simple-auth'] setting.