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!');
});