User Authentication with Rails and Backbone.js
Backbone.js is a small framework for developing web apps. Since there are no guiding conventions it can sometimes be difficult for the beginner to understand what is going on. The early contributors have different ideas of how things should be done. The community is still young, but over time sources for beginners will grow.
Backbone.js is used for almost all JavaScript front end development at 42Floors. One of the first problems that most web apps will encounter is creating and authenticating users. The following is our solution to this problem and is used with several of our internal tools.
Before we begin be sure you have Ruby and Ruby on Rails installed. If you are on a Mac, follow these instructions to get everything you need installed. You do not need to install MySQL because we will be using SQLite for this tutorial.
At 42Floors we have an internal MLS
(a database of available listings) for managing listings and properties. Let's
start by creating a Rails app named MLS
.
Bring up the Terminal and cd
to the directory which you want to create the
app in. For me, I keep all my code that I'm working on in a directory named
src
, so I'm going to cd ~/src
.
$ cd ~/src
$ rails new MLS
The rails new MLS
will generate an app named MLS in a directory called MLS
.
After creating the app set your current working directory to ~/src/MLS
by
cd
into it.
$ cd MLS
Next create the User
model that will be the basis for any account in our
system.
$ rails generate model user
The rails generate model user
command will create a migration
file in db/migrations
and a file in app/models
along with a few others.
Open up the migration file using a text editor of your choice (TextMate in
this case).
$ mate db/migrate/20120411200952_create_users.rb
You're migration file will have a different set of numbers at the beginning of the filename. This is just a timestamp to help differentiate between conflicting migration files. Update the file to include the added fields so it looks like the following:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, :null => false
t.string :email, :null => false
t.string :phone
t.string :company
t.string :title
t.string :license_id
t.string :linkedin
t.string :twitter
t.string :facebook
t.string :web
t.string :password_digest, :null => false
t.timestamps
end
end
end
After we run this migration a user will have to have the name
, email
, and
password_digest
, otherwise the user will not be created in the database. The
:null => false
tells the database to reject an insert if any of the fields
specified are null
. Next we will also add this validation to the Rails
app as well. The password_digest
field is where we will store the encrypted
password.
Save the file and then open app/models/user.rb
.
$ mate app/models/user.rb
Update the file to contain the following validations and methods.
class User < ActiveRecord::Base
attr_accessor :password
attr_protected :password_digest
validates :name, :presence => true
validates :email, :presence => true, :uniqueness => true, :email => true
validates :password, :presence => true, :confirmation => true
validates :password_confirmation, :presence => { :if => :password }
validates :phone, :format => { :allow_nil => true, :with => /^[\(\)0-9\- \+\.]{10,20}\s*[extension\.]{0,9}\s*[0-9]{0,5}$/i }
def password=(pass)
return if pass.blank?
@password = pass
self.password_digest = BCrypt::Password.create(pass)
end
end
The attr_accessor :password
specifies a virtual attribute
name password
. We won't be storing the password in plaintext, but we do need
to know what it is when the user is being created so we can encrypt it. The
password=
method will be encrypt the password with bcrypt
and store the encrypted password in the password_digest
field.
The validations make sure that the require fields are present and are the
correct format if necessary. The :presence => true
ensures an attribute is
not null, :confirmation => true
in this case ensures that password
matches
password_confirmation
, the :format
on the phone makes sure it matches the
give regex if it is not
null, and the :email => true
validates that the email looks like a real
email address.
Using the bcrypt and email validation functionality requires use of both the
bcrypt-ruby and the email_validator
gems. Open up the Gemfile
to add these dependencies to our application.
$ mate Gemfile
The Gemfile should look like this after adding these gems:
source 'https://rubygems.org'
gem 'rails', '3.2.3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
gem 'bcrypt-ruby', :require => 'bcrypt'
gem 'email_validator'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', :platform => :ruby
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
# To use Jbuilder templates for JSON
# gem 'jbuilder'
# Use unicorn as the app server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'
Rails uses Bundler as its dependency manager. We now
have to run the bundle
command to update all the gems for our Rails app.
$ bundle
Now lets migrate our database to add the users
table.
$ rake db:migrate
Now we have the ability to create users. Lets try it and make sure it works. Bring up the Rails console.
$ rails console
>> u = User.new(:name => 'James', :email => 'james@42floors.com')
>> u.save
=> false
Doesn't look like it worked. Lets take a look at the errors on the user.
>> u.errors.to_a
=> ["Password can't be blank"]
Forgot to set the password
, lets set it and the password_confirmation
now.
>> u.password = 'test'
>> u.password_confirmation = 'test'
>> u.save
=> true
Great, looks like its working. In the next step we will build the controller. After that we will start updating the views and working with Backbone.js.
Exit the Rails console if you are still in it. Then generate the controller.
>> exit
$ rails generate controller users
Open up the UsersController
.
$ mate app/controllers/users_controller.rb
Update the file:
class UsersController < ApplicationController
respond_to :html, :json
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
flash[:notice] = 'Account created.'
end
respond_with @user, :location => '/'
end
end
The respond_to :html, :json
specifies that this controller will respond to
both HTML and
JSON. The new
action will render a form for the user
to fill out and the create
action will receive a POST
request and send.
The next step is to setup the routes so that the URLs /signup
and /account/new
route to the new
action and the route /account
routes to the create
action. Open up the config/routes.rb
and update the routes:
MLS::Application.routes.draw do
match 'signup' => 'users#new', :via => :get
resource :account, :controller => 'users', :only => [:new, :create]
end
By default resource
will include the index
, show
, new
, edit
,
create
and destroy
methods. The :only => [:new, :create]
says that we
only want the new
and create
actions. You can learn more about routing
here.
That pretty much wraps up all the Rails work we are going to be doing for user accounts. Now we need to complete the views and integrate Backbone.js.
Before we go any further lets add Backbone.js, Underscore.js and the Rails
Backbone.js extension. I keep any libraries like jQuery, Backbone.js and
Underscore.js under the lib/assets/javascripts
directory since they don't
contain any application logic. We will need to create this directory first
before adding the files.
$ mkdir lib/assets/javascripts
Now just download the files:
$ curl http://documentcloud.github.com/backbone/backbone.js > lib/assets/javascripts/backbone.js
$ curl http://documentcloud.github.com/underscore/underscore.js > lib/assets/javascripts/underscore.js
$ curl https://raw.github.com/codebrew/backbone-rails/master/vendor/assets/javascripts/backbone_rails_sync.js > lib/assets/javascripts/backbone_rails_sync.js
app/assets/javascripts/application.js
needs to be updated to include these
files so our client side code will have access to Backbone.js.
$ mate app/assets/javascripts/application.js
The file should look like the following:
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone_rails_sync
There are two views that we are going to need to create. First is the signup
form (the url /signup
or /account/new
) and the second it the login
(/account
). Create new.html.erb
and open it.
$ mate app/views/users/new.html.erb
Now we'll add the form for creating the user.
<div id='signup-view'>
<div class="page-header">
<h1>42Floors <small>MLS</small></h1>
</div>
<%= form_tag account_path, :class => 'form-horizontal' do %>
<div class="control-group">
<%= label_tag :name, nil, :class => 'control-label' %>
<div class="controls">
<%= text_field_tag :name, nil, :placeholder => 'Robert A. Jones' %>
</div>
</div>
<div class="control-group">
<%= label_tag :email, nil, :class => 'control-label' %>
<div class="controls">
<%= text_field_tag :email, nil, :placeholder => 'robert@example.com' %>
</div>
</div>
<div class="control-group">
<%= label_tag :password, nil, :class => 'control-label' %>
<div class="controls">
<%= password_field_tag :password %>
</div>
</div>
<div class="control-group">
<%= label_tag :password_confirmation, 'Confirm', :class => 'control-label' %>
<div class="controls">
<%= password_field_tag :password_confirmation %>
</div>
</div>
<div class="form-actions">
<%= submit_tag "Signup", :class => 'btn btn-primary' %>
<div class='clear'></div>
</div>
<% end %>
<div class='clear'></div>
</div>
If you start the rails server
using rails server
you should see the following when you go to http://localhost:3000/signup
Lets add some styling to this. Open app/assets/stylesheets/users.css.scss
and add the following CSS.
#signup-view {
width: 470px;
margin: 0 auto;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
background: #FFF;
.page-header {
padding: 18px 1em;
margin: 0 0 18px 0;
border-bottom: 1px solid #EEE;
h1 { margin: 0; padding: 0; }
small { font-weight: normal; color: #999; }
}
label {
float: left;
width: 140px;
padding-top: 5px;
text-align: right;
}
.control-group {
margin-bottom: 18px;
}
.controls { margin-left: 160px; }
input[type=text], input[type=password] { width: 280px; }
input[type=submit] { display: inline-block; float: right; }
.form-actions {
padding: 17px 20px 18px;
margin-top: 18px;
margin-bottom: 18px;
background-color: #EEE;
border-top: 1px solid #DDD;
}
}
.clear { clear: both; }
Save this file and then take a look at http://localhost:3000/signup
. The
page should look like the image below.
Now lets start creating the first Backbone.js view, SignupView
. I like to
keep my javascript files organized under the app/assets/javascripts
directory similar to how Rails organizes its controllers, views, and models
under the app/
directory. Create the folders models
and views
in
app/assets/javascripts
.
$ mkdir app/assets/javascripts/models
$ mkdir app/assets/javascripts/views
Create the signupView.js
file and open it for editing.
$ mate app/assets/javascripts/views/signupView.js
Update the file to define our view:
SignupView = Backbone.View.extend({
el: '#signup-view',
initialize: function () {
this.form = this.$el.find('form');
this.nameField = this.$el.find('input[name=name]');
this.emailField = this.$el.find('input[name=email]');
this.passwordField = this.$el.find('input[name=password]');
this.passwordConfirmationField = this.$el.find('input[name=password_confirmation]');
this.submitButton = this.$el.find('input[type=submit]');
},
});
The el
is the DOM element that the view will attach to. Whenever we create
the view the initialize
function will get called. In the initialize
function I just create references to the input fields so I don't have to make
a call to jQuery again. Later on we will use these references to retrieve the
values the user entered.
Before we go any further the initialization process needs to be created. We
have defined our view but never used it. Open
app/assets/javascripts/application.js
. Edit the file to include the following:
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone_rails_sync
//= require_tree ./models
//= require_tree ./views
// Rails CSRF Protection
$(document).ajaxSend(function (e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
// Underscore.js Template Settings
_.templateSettings = {
interpolate: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\{(.+?)\}\}/g
};
// Routing Based on URL
Router = {
'/signup': function () { new SignupView(); },
route: function (path) {
_.each(Router, function(callback, route) {
if (!_.isRegExp(route)) {
route = Backbone.Router.prototype._routeToRegExp(route);
}
if(route.test(path)) {
var args = Backbone.Router.prototype._extractParameters(route, path);
callback.apply(this, args);
}
});
}
};
// Start the app when the page has loaded.
$(document).ready(function () {
Router.route(window.location.pathname);
});
The CSRF Protection
is a security feature in Rails. This function just tells jQuery to include the
CSRF token that is rendered in the head
of the HTML doc whenever an AJAX)
request is made. If this isn't included the request will throw an error.
By default Underscore.js uses the <% %>
syntax for evaluation and <%= %>
for interpolating. This gets confusing when working with Rails as it uses the
same templating syntax. This changes Underscore.js to use the {{ }}
and
{{= }}
syntax.
The Router
is a custom router that just takes routes a URL to a given
callback.
Now lets create our User
model for Backbone.js that will be used to create
a user from what is entered in the signup form. Open
app/assets/javascripts/models/user.js
.
$ mate app/assets/javascripts/models/user.js
And add the model definition.
var User = Backbone.Model.extend({
url: '/account',
paramRoot: 'user'
});
The url
is the route that should be used for the model. For a model called
Property
the URL would probably be /properties
.
The paramRoot
is part of the Rails extension to Backbone.js. When creating
or updating a model Rails typically expects the parameters to be scoped. So
any parameters for the user sent to Rails will be scoped under the user
name.
Thats all that is needed to start creating user accounts for the model. Lets now go back to the view and create a user when the submit button is clicked.
Open the app/assets/javascripts/views/signupView.js
. Add the events
,
attributes
, and createUser
properties to the view:
SignupView = Backbone.View.extend({
el: '#signup-view',
events: { 'submit form': 'createUser' },
attributes: function () {
return {
name: this.nameField.val(),
email: this.emailField.val(),
password: this.passwordField.val(),
password_confirmation: this.passwordConfirmationField.val()
};
},
createUser: function () {
if (this.submitButton.hasClass('disabled') && this.form.data('user-created') !== true) {
return false;
} else {
this.submitButton.addClass('disabled');
}
var self = this,
user = new User(this.attributes());
user.save(null, {
error: function (originalModel, resp, options) {
self.$el.find('input').removeClass('error');
var errors = JSON.parse(resp.responseText).errors;
_.each(errors, function(value, key) {
self.$el.find('input[name=' + key +']').addClass('error');
});
self.submitButton.removeClass('disabled');
},
success: function () {
self.form.data('user-created', true);
document.location.href = '/';
}
});
return (this.form.data('user-created') === true);
},
initialize: function () {
this.form = this.$el.find('form');
this.nameField = this.$el.find('input[name=name]');
this.emailField = this.$el.find('input[name=email]');
this.passwordField = this.$el.find('input[name=password]');
this.passwordConfirmationField = this.$el.find('input[name=password_confirmation]');
this.submitButton = this.$el.find('input[type=submit]');
},
});
The events
property is essentially a hash which binds the given events
(the key) to the the specified function. In this case the event is a submit
on the form
element. When that event is fired, createUser
will be called.
The createUser
function at first disables the button so that if the user
hits it again while the browser won't send the a second request trying to
create the same user again. If the button is disabled, just return and do
nothing, otherwise continue to create the user. The line
new User(this.attributes());
creates a new user model that has the
attributes specified in the attributes
function. After that user.save
is
called. The function is given two callbacks, error
and success
. On
error
the server returns the fields that have errors and simply adds a the
.error
class to the input field. In the CSS you could then highlight the
field in red so the user is aware of what the error is. On success the user
gets redirect to the root of the application.
Lets update the CSS to highlight a input.error
field in red. Open
app/assets/stylesheets/users.css.scss
. Add the following line under the #signup-view
input.error { border: 2px solid red; }
Go to http://localhost:3000/signup
and try submitting the form. The fields
with errors should now be highlighted.
Go ahead and fill out the form. If it was successful you should see the standard welcome to Rails page.
Creating a user is now completed. Developing the login form is next.
First open the ApplicationController
in app/controllers/application_controller.rb
and
add the following helper methods that will support user sessions.
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def require_user
return if current_user
respond_to do |format|
format.html { redirect_to login_path }
format.all { render :text => 'unauthorized', :status => :unauthorized }
end
end
def current_user
return @current_user if @current_user
if session[:user_id]
@current_user = User.find(session[:user_id])
elsif (header = request.headers['Authorization'].to_s.sub('Basic ','')) != ''
header = Base64.decode64(header).split(':')
username = header.shift
password = header.join(':')
@current_user = User.authenticate(username, password)
end
end
def create_user_session(user)
session[:user_id] = user.id
end
def destroy_user_session
session[:user_id] = nil
end
end
The require_user
can be used in a before_filter
to only allow access to logged in users, current_user
returns the current
logged in user, create_user_session
will create a session for a given user,
and destroy_user_session
will log out a user.
Now create a SessionsController
that will manage the creation of user
sessions.
$ rails generate controller sessions
$ mate app/controllers/sessions_controller.rb
Add the create
and destroy
actions which will be used for logging in and
logging out.
class SessionsController < ApplicationController
respond_to :html, :json
def create
@user = User.authenticate(params[:email], params[:password])
if @user
create_user_session(@user)
respond_with @user, :location => '/', :notice => "Login succesful."
else
respond_to do |format|
format.html { render 'new' }
format.json { render :json => {:error => "Invalid email or password."} }
end
end
end
def destroy
destroy_user_session
redirect_to '/', :notice => "Logged out."
end
end
The User.authenticate
method hasn't been defined yet. Open up the user model
for Rails and add it.
class User < ActiveRecord::Base
attr_accessor :password
attr_protected :password_digest
validates :name, :presence => true
validates :email, :presence => true, :uniqueness => true, :email => true
validates :password, :presence => true, :confirmation => true
validates :password_confirmation, :presence => { :if => :password }
validates :phone, :format => { :allow_nil => true, :with => /^[\(\)0-9\- \+\.]{10,20}\s*[extension\.]{0,9}\s*[0-9]{0,5}$/i }
def self.authenticate(email, pass)
user = where(:email => email).first
user && BCrypt::Password.new(user.password_digest) == pass ? user : nil
end
def password=(pass)
return if pass.blank?
@password = pass
self.password_digest = BCrypt::Password.create(pass)
end
end
Don't forget about the routes in config/routes.rb
.
MLS::Application.routes.draw do
match 'login' => 'sessions#new', :via => :get
match 'logout' => 'sessions#destroy', :via => [:get, :delete]
match 'signup' => 'users#new', :via => :get
resource :session, :only => [:new, :create, :destroy]
resource :account, :controller => 'users', :except => [:index, :destroy, :show, :edit]
end
Now back to the views and Backbone.js. Create the app/views/sessions/new.html.erb
file and open it up for editing.
$ mate app/views/sessions/new.html.erb
Create the view.
<div id='login-view'>
<aside>
<h1>42Floors MLS</h1>
</aside>
<%= form_tag session_path do %>
<%= label_tag :email %>
<%= text_field_tag :email, params[:email] %>
<%= label_tag :password %>
<%= password_field_tag :password %>
<%= submit_tag "Log In" %>
<% end %>
<div class='clear'></div>
</div>
Add some styling in app/assets/stylesheets/sessions.css.scss
.
#login-view {
width: 670px;
position: relative;
margin: 100px auto;
background: #FFF;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
background: #FFF;
aside {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 270px;
float: left;
margin: 0;
text-align: center;
padding: 75px 0;
margin-right: 15px;
}
form {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
width: 385px;
float: left;
padding: 30px 30px 30px 45px;
border-left: 1px solid #D8DEE2;
label { width: 310px; display: inline-block; margin-top: 10px; }
input { margin-bottom: 10px; }
input[type=text], input[type=password] { width: 300px }
input[type=submit] {
float: right;
margin-top: 10px;
display: block;
}
}
}
Now the Backbone.js side of things. Create and open app/assets/javascripts/views/loginView.js
.
$ mate app/assets/javascripts/views/loginView.js
Define the view.
LoginView = Backbone.View.extend({
el: '#login-view',
events: { 'submit form': 'authorize' },
authorize: function () {
if (this.submitButton.hasClass('disabled') && !(this.form.data('user-authorized') === true)) {
return false;
} else {
this.submitButton.addClass('disabled');
}
var self = this,
attrs = {
email: this.emailField.val(),
password: this.passwordField.val()
};
User.authorize(attrs, function (err, user) {
if (err) { self.loginFailure(); }
else { self.loginSuccess(); }
});
return (this.form.data('user-authorized') === true);
},
loginSuccess: function () {
this.form.data('user-authorized', true);
this.form.submit();
},
loginFailure: function () {
this.$el.animate({left: '-=20'}, 100);
this.$el.animate({left: '+=40'}, 100);
this.$el.animate({left: '-=40'}, 100);
this.$el.animate({left: '+=40'}, 100);
this.$el.animate({left: '-=20'}, 100);
this.emailField.focus();
this.submitButton.removeClass('disabled');
},
initialize: function () {
this.form = this.$el.find('form');
this.emailField = this.$el.find('input[name=email]');
this.passwordField = this.$el.find('input[name=password]');
this.submitButton = this.$el.find('input[type=submit]');
},
});
The authorize
function will try to authenticate the user with the Rails app.
If it succeeds the user will be redirected to a different page. If it does not
succeed the login form will shake and require the user to try and authenticate
again.
Update the Backbone.js routes in app/assets/javascripts/application.js
to
initialize the view.
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require backbone_rails_sync
//= require_tree ./models
//= require_tree ./views
$(document).ajaxSend(function (e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
_.templateSettings = {
interpolate: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\{(.+?)\}\}/g
};
Router = {
'/signup': function () { new SignupView(); },
'/login': function () { new LoginView(); },
route: function (path) {
_.each(Router, function(callback, route) {
if (!_.isRegExp(route)) {
route = Backbone.Router.prototype._routeToRegExp(route);
}
if(route.test(path)) {
var args = Backbone.Router.prototype._extractParameters(route, path);
callback.apply(this, args);
}
});
}
};
$(document).ready(function () {
Router.route(window.location.pathname);
});
Now if you try logging in, it still won't work. The User.authorize
for the
Backbone.js model hasn't been defined yet, so the browser will just throw an
error and then submit the form. Open up app/assets/javascripts/models/user.js
and add the function definition.
var User = Backbone.Model.extend({
url: '/account',
paramRoot: 'user',
authenticate: function (password, callback) {
var self = this;
$.ajax({
type: 'POST',
url: '/session.json',
data: {
email: this.get('email'),
password: password
},
success: function (data) {
if (data.error) {
callback.call(this, data.error, self);
} else {
self.set(data);
callback.call(this, null, self);
}
}
});
},
});
User.authorize = function (attrs, callback) {
var user = new User({email: attrs.email});
user.authenticate(attrs.password, callback);
};
The authenticate
method will submit an AJAX) request and ensure that the
credentials are correct. The User.authorize
is just a helper method so
it isn't necessary to directly instantiate a new User model.
Now go to http://localhost:3000/login
in a browser. It should look like this.
Hit the submit button on the form. The form should shake letting you know that the credentials were incorrect.
Sign up for the app at http://localhost:3000/signup
. Then go to
http://localhost:3001/login
. If you enter the correct credentials it will
let you through.
This is only one of the ways to do authentication with Rails.
One downside to this approach is that after the user is authenticated the same request is sent again to the server will redirect to the home page of the app.
A different approach would be to render the view with Backbone.js on the client side. This can get sticky because the application has state. If the website doesn't need to be indexed by Google, Bing, or Yahoo it works fine. If it does then the server can render the view with all the data and the app can be support multiple entry points based on the URL. This can also get complicated as every page can either be rendered on the client side (which has state) or the server (which does not have state) and both need to end up outputting the same thing. I haven't found a good way to deal with this yet.
To learn more about Backbone.js, visit the docs. Peepcode also has some great screencasts. They don't use the most recent version of Backbone.js, but its still full of great information. Some folks have even started writing a free eBook. Check it out for a deeper dive into Backbone.js.
A quick note about security. By default Rails uses cookies to store session data. The user will be able to see what is stored in the cookie. However Rails does sign the cookie to prevent the user from tampering with it.
HTTPS should also be used on the login and
signup pages. The downside of this is that you may run into issues when
caching parts of the page. Ideally we would just make the AJAX) request when
the users submits the form over HTTPS. The force_ssl
feature in Rails
can be used to force an action to use HTTPS.