Integrating Devise with Backbone.js

For the impatient: view the code, or see the demo.

Update 30 May 2012: The original version of the application and this article referenced the backbone.modelbinding project by Derick Bailey. Per the readme, that project has been abandoned and I have replaced backbone.modelbinding with the preferred backbone.modelbinder project. All code and referenced samples below have been updated accordingly.

Backbone.js UI implementation for Devise, Ruby on Rails authentication

Several weeks ago I set out to create a single page web app leveraging Backbone.js on the front end with Ruby on Rails powering the back end. In the architecture for this application, Rails is essentially just a JSON API. All of the front-end magic happens with Backbone.js and related friends. Granted, Rails is doing a lot more boilerplate than just acting as an API, but the separation of the UI code and back-end code is a lot clearer in this model.

One of the first things I need on almost all new projects is a way to handle user registration, authentication, and authorization. This project was no different. Fortunately, the Devise Ruby Gem handles this for us in a very clean, configurable way, and to make matters even better, it natively responds to JSON calls. This made integrating it into my single page web app a breeze.

Arguably, doing this is unnecessary as Devise ships with its own UI implementation. However, for the sake of UI consistency and richness, re-implementing it in Backbone is fairly trivial and was a good learning experience. I've extracted this part of the app into a sample rails project which can be found at https://github.com/jhuckabee/backbone_devise. I've also published a working demo at http://backbonedevise.herokuapp.com.

To implement the Devise UI in Backbone.js I started by creating 3 Backbone models, one each for authentication, registration, and password retrieval.

UserRegistration:

  BD.Models.UserRegistration = Backbone.Model.extend({
    url: '/users.json',
    paramRoot: 'user',

    defaults: {
      "email": "",
      "password": "",
      "password_confirmation": ""
    }
  });

UserSession:

BD.Models.UserSession = Backbone.Model.extend({
  url: '/users/sign_in.json',
  paramRoot: 'user',

  defaults: {
    "email": "",
    "password": ""
  }

});

UserPasswordRecovery:

BD.Models.UserPasswordRecovery = Backbone.Model.extend({
  url: '/users/password.json',
  paramRoot: 'user',

  defaults: {
    "email": ""
  }
});

For each of the models I created a corresponding view and used the modelbinding plugin to bind the model attributes to their respective form elements. This left the view with only being responsible for handling form submissions and taking appropriate action based on the response from Rails. In this case I'm just displaying appropriate error messages or updating the layout as a result of a successful login.

Here is the login view:

BD.Views.Unauthenticated = BD.Views.Unauthenticated || {};

BD.Views.Unauthenticated.Login = Backbone.Marionette.ItemView.extend({
  template: 'unauthenticated/login',

  events: {
    'submit form': 'login'
  },

  initialize: function() {
    this.model = new BD.Models.UserSession();
    this.modelBinder = new Backbone.ModelBinder();
  }

  onRender: function() {
    this.modelBinder.bind(this.model, this.el);
  },

  login: function(e) {

    var self = this,
        el = $(this.el);

    e.preventDefault();

    el.find('input.btn-primary').button('loading');
    el.find('.alert-error').remove();

    this.model.save(this.model.attributes, {
      success: function(userSession, response) {
        el.find('input.btn-primary').button('reset');
        BD.currentUser = new BD.Models.User(response);
        BD.vent.trigger("authentication:logged_in");
      },
      error: function(userSession, response) {
        var result = $.parseJSON(response.responseText);
        el.find('form').prepend(BD.Helpers.Notifications.error(result.error));
        el.find('input.btn-primary').button('reset');
      }
    });

  }

});

It's also important to point out that in addition to Backbone.js, I'm also using a few additional libraries to help with application structure and Rails integration. These are:

  • backbone.marionette:
    Bakes in some nice implementation patterns for dealing with large
    javascript applications like the notion of layouts, composite/item views,
    regions, and application-wide pub/sub.
  • backbone.modelbinder:
    Provides a simple way to bind model attributes to HTML elements a la Knockout.js.
  • backbone-rails' sync implementation:
    The backbone-rails gem provides an out-of-the-box solution for integrating
    Rails and Backbone.js. I'm only using their Backbone.sync implementation
    which makes Backbone.js and Rails play nicely together when talking back
    and forth.

So far, I feel like these technologies are playing very nicely together. Rails has made flushing out my own JSON API's extremely easy, and Backbone has made working on the client side a much more clean and enjoyable experience.

Comments

Great article, and just in time

I am learning backbone, and this will help me to integrate devise with backbone. Thanks.

Thanks

Thanks for this article, as a beginner in backbone it has been very helpful

Issue on Github

Hi Josh,

I raised an issue on the Github repo listed above. I was wondering if you have seen it yet?

I am guessing I am doing something obvious but was hoping you might be able to spot it.

Cheers,

Luke

emberjs version

Nice post! can you provide emberjs version?

GREATTTT

very helpful, thanks you..!

Backbone.CollectionElementBinder

For collection HTML element binding you can try https://github.com/sergey-trotsyuk/Backbone.CollectionElementBinder

Migration to coffee-script

Hi,

I just generated a version which is using coffee script

https://github.com/dei79/backbone_devise

Thanks for the good sample

Cheers
Dirk

How about adding omniauth

How about adding omniauth integration for Facebook Twitter login?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options