I am building an ember.js application and am hung up on authentication. The json rest backend is rails. Every request is authenticated using a session cookie (warden).
It's not addressed by ember-data (and probably won't be), but you can reopen the DS class and extend the ajax method.
It looks like this:
ajax: function(url, type, hash) {
hash.url = url;
hash.type = type;
hash.dataType = 'json';
hash.contentType = 'application/json; charset=utf-8';
hash.context = this;
if (hash.data && type !== 'GET') {
hash.data = JSON.stringify(hash.data);
}
jQuery.ajax(hash);
},
You can rewrite it with something like this (disclaimer: untested, probably won't work):
DS.reopen({
ajax: function(url, type, hash) {
var originalError = hash.error;
hash.error = function(xhr) {
if (xhr.status == 401) {
var payload = JSON.parse(xhr.responseText);
//Check for your API's errorCode, if applicable, or just remove this conditional entirely
if (payload.errorCode === 'USER_LOGIN_REQUIRED') {
//Show your login modal here
App.YourModal.create({
//Your modal's callback will process the original call
callback: function() {
hash.error = originalError;
DS.ajax(url, type, hash);
}
}).show();
return;
}
}
originalError.call(hash.context, xhr);
};
//Let ember-data's ajax method handle the call
this._super(url, type, hash);
}
});
What we're doing here is essentially deferring the call that received the 401 and are preserving the request to be called again when login is complete. The modal's ajax call with have the original error applied to it from the original ajax call's hash, so the original error would still work as long as it's defined :-)
This is a modified implementation of something we're using with our own data-persistence library, so your implementation might vary a bit, but the same concept should work for ember-data.
For those willing to accept a solution that does lose changes and state you can register a jQuery ajaxError handler to redirect to a login page.
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
// You should include additional conditions to the if statement so that this
// only triggers when you're absolutely certain it should
if (jqXHR.status === 401) {
document.location.href = '/users/sign_in';
}
});
This code will get triggered anytime any jQuery ajax request completes with an error.
Of course you would never actually use such a solution as it creates an incredibly poor user experience. The user is yanked away from what they're doing and they lose all state. What you'd really do is render a LoginView, probably inside of a modal.
An additional nicety of this solution is that it works even if you occasionally make requests to your server outside of ember-data. The danger is if jQuery is being used to load data from other sources or if you already have some 401 error handling built-in elsewhere. You'll want to add appropriate conditions to the if statement above to ensure things are triggered only when you're absolutely certain they should.
As of the current version of Ember Data (1.0 beta) you can override the ajaxError
method of DS.RESTAdapter
:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 401) {
#handle the 401 error
}
return error;
}
});
Note that you should call @_super
, especially if you are overriding one of the more complex adapters like DS.ActiveModelAdapter
, which handles 422 Unprocessable Entity
.
AFAIK this is not addressed by the current implementation of ember-data and the ember-data README states that "Handle error states" is on the Roadmap.
For the time being, you can implement your own error handling adapter. Take a look at the implementation of the DS.RestAdapter . By using that as a starter, it should not be too difficult to add error handling in there (e.g simply add an error function to the the data hash that is passed to the jQuery.ajax call).