Send token with every backbone sync request

前端 未结 4 1414
名媛妹妹
名媛妹妹 2020-12-13 05:01

My PHP api requires a user token be submitted with every request from my front-end Backbone app to make sure the user...

  1. Is active
  2. Has permissions
相关标签:
4条回答
  • 2020-12-13 05:41
    Backbone.$.ajaxSetup({
       headers: { 'sid': 'blabla' }
    });
    
    0 讨论(0)
  • 2020-12-13 05:51

    Authentication is a responsibility of the app.

    For a Backbone app, the auth logic should be within the Backbone code and changing the global jQuery's ajax behavior should be avoided at all cost.

    Cons of ajaxSetup or ajaxSend

    From the jQuery doc on ajaxSetup:

    Note: The settings specified here will affect all calls to $.ajax or Ajax-based derivatives such as $.get(). This can cause undesirable behavior since other callers (for example, plugins) may be expecting the normal default settings. For that reason we strongly recommend against using this API. Instead, set the options explicitly in the call or define a simple plugin to do so.

    ajaxSend has the same problem as mentioned above. The only advantage it has over ajaxSetup is calling a function each time, giving you more flexibility than the object based options passed to ajaxSetup.

    The safest way, the AuthModel and AuthCollection

    Put the authentication logic into a base model and collection. This is the most scoped solution.

    Here, you could use your already existing BaseModel, but I'd still favor separating the BaseModel from the AuthModel as you may want to create a custom model which uses your base model but also uses a different external API for example.

    Since the new sync function for the model and the collection are similar but both may have a different parent implementation, I made a simple function generator.

    /**
     * Generates a new sync function which adds the token to the request header 
     * and handles a redirect on error.
     * @param  {Function} syncFn the parent `sync` function to call.
     * @return {Function}  a new version of sync which implements the auth logic.
     */
    var authSyncFunction = function(syncFn) {
        return function(method, model, options) {
            options = options || {};
    
            var beforeSend = options.beforeSend,
                error = options.error;
    
            _.extend(options, {
                // Add auth headers
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('Authorization', "Bearer " + yourTokenHere);
                    if (beforeSend) return beforeSend.apply(this, arguments);
                },
    
                // handle unauthorized error (401)
                error: function(xhr, textStatus, errorThrown) {
                    if (error) error.call(options.context, xhr, textStatus, errorThrown);
                    if (xhr.status === 401) {
                        Backbone.history.navigate('login');
                    }
                }
            });
    
            return syncFn.call(this, method, model, options);
        };
    };
    

    Use the generator on both a model and a collection.

    var AuthModel = BaseModel.extend({
        sync: authSyncFunction(BaseModel.prototype.sync)
    });
    
    var AuthCollection = BaseCollection.extend({
        sync: authSyncFunction(BaseCollection.prototype.sync)
    });
    

    Then you're ready to use these on models and collection you're sure will need authentication. Since you were already using a base model and collection, it would be just a matter of changing the BaseModel.extend to AuthModel.extend.

    While I know you asked for a redirect on a 403 Forbidden response, I think it should be on a 401 Unauthorized. See 403 Forbidden vs 401 Unauthorized HTTP responses

    Overriding Backbone's sync

    If you don't feel like changing all models and collections at this point, but still want to follow good practices and avoid changing the global ajax setup, overriding the Backbone.sync function is an easy alternative.

    Using our previously defined sync generator:

    Backbone.sync = authSyncFunction(Backbone.sync);
    

    Managing the local storage and the authentication

    To manage the data in the local storage, check Backbone-session.

    It's a nice implementation of a Backbone model which syncs with the local storage instead of a REST API. It also provides a nice interface to manage the authentication.

    // Extend from Session to implement your API's behaviour
    var Account = Session.extend({
      signIn: function () {},
      signOut: function () {},
      getAuthStatus: function () {}
    });
    
    // Using the custom Account implementation
    var session = new Account();
    session.fetch()
      .then(session.getAuthStatus)
      .then(function () {
        console.log('Logged in as %s', session.get('name'));
      })
      .fail(function () {
        console.log('Not yet logged in!');
      });
    
    0 讨论(0)
  • 2020-12-13 05:52

    You could do this:

    var _sync = Backbone.sync;
    Backbone.sync = function(method, model, options) {
    
        if( model && (method === 'create' || method === 'update' || method === 'patch') ) {
            options.contentType = 'application/json';
            options.data = JSON.stringify(options.attrs || model.toJSON());
        }
    
        _.extend( options.data, {
            "access_token": "some-token"
        });
    
        return _sync.call( this, method, model, options );
    }
    

    And just listen for the fail event of fetch/save method to redirect a user to /login

    model.fetch().fail( /* redirect */ )
    
    0 讨论(0)
  • 2020-12-13 06:02

    Backbone uses jQuery's $.ajax, so you can use $.ajaxSetup to "set default values for future Ajax requests":

    $.ajaxSetup({
       headers: {
         "accept": "application/json",
         "token": YOUR_TOKEN
       }
    });
    

    Update: an improvement to this idea (thanks to @Glen) is to use $.ajaxSend to check for the existence of a token each time before setting it in the headers of the request:

    $(document).ajaxSend(function(event, request) {
       var token = App.getAuthToken();
       if (token) {
          request.setRequestHeader("token", token);
       }
    });
    

    Where App.getAuthToken() is a function in your Backbone app.

    0 讨论(0)
提交回复
热议问题