1

I've got a simple Backbone.js app which uses json-server as a backend. I have a function to log in, that finds a user from the collection, but I don't get how will I save my session. I thought about storing a parameter in cookies that later will be checked on every redirect. Here is my model:

var User = Backbone.Model.extend({
    defaults: {
        login: '',
        password: '',
        authToken: ''
    }
});

And here is my collection:

var UserCollection = Backbone.Collection.extend({
    url: 'http://localhost:3000/users',

    // creates a random token
    setToken: function () {
        var rand = function () {
            return Math.random().toString(36).substr(2)
        }

        var token = rand() + rand();
        this.set({authToken: token});
    }
});

And this is the view with login function

var LoginView = Backbone.View.extend({
    initialize: function () {
        this.collection = new UserCollection();
        // template
    }
    // render function omitted

    signIn: function () {
        var login = $('#login').val();
        var password = $('#password').val();

        /** 
           finds a user within with the values from input fields
           inside the collection 
        */
        if (login && password) {
            this.collection.fetch({
                data: {
                    login: login,
                    password: password
                }
            });
        }
    }
});

This function returns me an array with one object that is my requested model. All that I need is to use my setToken method and to save this model's authToken in cookie so that I could use it elsewhere in app, but I don't actually get how to do that.

AlexNikolaev94
  • 1,169
  • 2
  • 16
  • 40

1 Answers1

1

Using a model to handle authentication would make more sense than a collection. Keep the model's responsibility simple and scoped to one thing. A model to handle the authentication, then a model to handle calls to other object which needs to be authenticated, not both at once.

I personally based authentication on Backbone-session's model.

// Using CommonJS
var Session = require('backbone-session');

// Extend from Session to implement your API's behaviour
var Account = Session.extend({
    urlRoot: 'http://localhost:3000/users',
    signIn: function(opt) {
        opt = opt || {};
        opt.data = _.extend({}, {
            login: opt.login,
            password: opt.password
        }, opt.data);
        return this.fetch(opt);
    },
    signOut: function(opt) { /** handle logout */ },
    getAuthStatus: function() { /** handle refetching if needed */ }
});

Which I expose as a service to my application. In this session module, I override Backbone.Sync to ensure auth for each following calls to the API for any models or collection.

var mySession = new Account();


Backbone.sync = (function(syncFn) {
    return function(method, model, options) {
        options = options || {};

        var beforeSend = options.beforeSend,
            error = options.error;

        // Add auth headers
        options.beforeSend = function(xhr) {
            xhr.setRequestHeader('Authorization', "Bearer " + mySession.get('authToken'));
            if (beforeSend) return beforeSend.apply(this, arguments);
        };

        // handle unauthorized error (401)
        options.error = function(xhr, textStatus, errorThrown) {
            if (error) error.call(options.context, xhr, textStatus, errorThrown);
            if (xhr.status === 401) {
                mySession.signOut();
            }
        };

        return syncFn.apply(this, arguments);
    };
})(Backbone.sync);

Backbone-session's model uses the local storage as a backend. Its own sync method is overriden to use the local storage instead of the default sync behavior.

sync: function(method, model, options) {
  options = options || {};
  var url = model.options.url || model.url;
  var key = _.isFunction(url) ? url() : '' + url;
  var response;
  switch (method) {
    case 'create':
    case 'update':
      var data = model.toJSON();
      var text = JSON.stringify(data);
      response = localStorage.setItem(key, text);
      break;
    case 'delete':
      response = localStorage.removeItem(key);
      break;
    case 'read':
      response = JSON.parse(localStorage.getItem(key));
      break;
  }
  if (_.isFunction(options.success)) {
    options.success(response);
  }
  return Backbone.$.Deferred()
    .resolve(response)
    .promise();
},

Why the local storage?

You could use this implementation and change it minimally to use cookies instead.

The local storage was a better option for me since my API is on another domain and uses CORS to enable public access. Safari has limitation on cookies.

Safari also blocks cookies from sites that haven't been visited directly. You can see in the security settings. It's default setting is Accept cookies: "Only from sites I visit".

Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129