Ember Ajax and Ember Simple Auth
It can be tricky to properly configure ember-ajax
to play nicely with ember-simple-auth
. You will need to manually configure a service to mimic the authorisation functionality you get for free when using Ember Simple Auth with Ember Data. This post will show you how to get up and running properly.
Update
This post has become a little dated as Ember and ESA have had a couple of big updates since this was first written. YMMV.
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
.
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
import AjaxService from "ember-ajax/services/ajax";
export default AjaxService.extend({});
Let’s customise if for our application:
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({
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:
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({
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 what 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:
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({
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.