emberjs handle 401 not authorized

2019-02-03 10:32发布

问题:

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).

When a user first navigates to the application root rails redirects to a login page. Once the session is authorized the ember.js app is loaded. Once loaded the ember.js app makes requests to the backend using ember-data RESTadapter and the session for authorization.

The problem is the session will expire after a predetermined amount of time. Many times when this happens the ember.js app is still loaded. So all requests to the backend return a 401 {not autorized} response.

To fix this problem I am thinking the ember.js app needs to notify the user with a login modal every time a 401 {not autorized} response is returned from the server.

Does anyone know how to listen for a 401 {not autorized} response and allow the user to re-login without losing any changes or state.

I have seen other approaches such as token authorization but I am concerned with the security implications.

Anybody have a working solution to this problem?

回答1:

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).



回答2:

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.



回答3:

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.



回答4:

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.