Ember + Rails + devise token authentication, first step working together in harmony

I've decided to refactor fitlogr into a Single Page Application (SPA) and in the process have encountered pretty much nothing familiar. Not only did I never learn javascript prior to this effort, apparently there's a whole new language, CoffeeScript, that has become the darling of rails devs looking to do more client-side processing. Just to keep this short, I'll partially list out the things that I had no experience with before deciding to refactor:

Since I only work on this at night, after I come home from my day job, getting to the point I'm about to describe has taken months of long nights. You should feel lucky that I will gloss over all the wrong paths that I've gone down.

For completeness I'll detail all the code that's needed for this to work. A copy of the code in this state is available here: https://github.com/avitevet/ember_devise_example, under the tag "blog_1."

Software Tools


I actually have two machines. The primary machine OS is Windows 7 Ultimate x64. I develop in a VMWare Player 5.0.0 build-812388 instance, using Ubuntu 12.10 beta (9/27). First helpful tip: apparently VMWare & Ubuntu don't play well together when you set # of processors > 1. I got tons of VM lockups. It happened every 15 minutes or less. Setting the # of processors = 1 fixed the problem.

The secondary machine came preinstalled with Windows, but I used wubi to install Ubuntu 12.04 LTS on it and now never boot into Windows.

Besides the hardware and OS differences, I think that I have created similar, if not identical, development environments on the two machines.

I use rvm to easily switch amongst ruby versions. Since fitlogr is deployed on heroku, I try to use the same tools and versions as the heroku cedar stack. I use ruby-1.9.2-p320 and Postgresql 9.1. Heroku's deployment process is super easy, and provides a built-in remote backup for all my source code. It's based on git so I use that for my source control.

I recently switched to Eclipse from emacs (gasp!). Don't worry, I miss emacs and still use it daily. But, IMO eclipse has better support for syntax highlighting of CoffeeScript files, as well as a better file explorer.

On that topic, I decided to use CoffeeScript rather than native javascript for the majority of my development because I prefer the no-braces look, and I'm not doing anything too complicated right now. Also, since I had next to zero experience with javascript there were equal learning curves to both, or maybe less for CoffeeScript because it abstracts away things like "existence."

I'm using Rails 3.2.3. I won't list all the gems I'm using here, except to mention that I'm using devise for authentication, capybara/konacha for javascript testing, Test::Unit for rails testing, and ember.js for the client framework (more below).

Client framework

I spent some time briefly reviewing the various client side frameworks, of which there are many good ones. I liked backbone.js for the large community, but eventually decided on ember.js for the amazing features. Seriously, Handlebars with two way bindings is like magic.

Tests first, but a new rails app zero-th

In the true spirit of test-driven development, I'll describe the tests that I wrote first, right after setting up a new rails app.  In reality-land, just learning the testing frameworks and tools took weeks of time.  In blog-land, you can benefit from my learnings and see only what I was able to get working.

Here's the Getting Started with Rails guide, which you can read for details.

After using rvm to select ruby 1.9.2:

rvm use 1.9.2

I created a new app using postgresql for the database.

rails new ember_devise_example -d postgresql

In the config/database.yml file, for ease of development, I'm changing the username to 'avi' which is my current login (aka the output of whoami).

# config/database.yml
development:
  adapter: postgresql
  encoding: unicode
  database: ember_devise_example_development
  pool: 5
  username: avi
  password:

test:
  adapter: postgresql
  encoding: unicode
  database: ember_devise_example_test
  pool: 5
  username: avi
  password:


Heroku overwrites the production entry so I'll ignore the whole production entry.

Just for reference, in my pg_hba.conf file, I'm using peer & trust authentication, which allows me to connect to the postgresql database without entering a password.  Fine for development but not ok for production.  BTW, if you get a "fe_sendauth: no password supplied" error at any time with the above config/database.yml, it's probably because you need to change the pg_hba.conf file.  If you do change the file, remember to restart the database server.

# /etc/postgresql/9.1/main/pg_hba.conf
# Database administrative login by Unix domain socket
local   all             postgres                                peer

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust

Then restart the server.

sudo -u postgres /etc/init.d/postgresql restart

Now we can create the database and start integrating devise.

rake db:create:all
rake db:migrate

Add devise to the Gemfile:
# Gemfile
   ...
   gem 'devise'
   ...
and then run the bundle command.

bundle install

I'll assume that you follow the directions linked above for adding the gem to the Gemfile and running the bundle command to install the gem.  Run the devise generator:

rails generate devise:install

It outputs a bunch of recommendations, which we'll perform now.

# config/environments/{development, test}.rb
# add this line somewhere inside the Application.configure block
config.action_mailer.default_url_options = { :host => 'localhost:3000' }

Since this will be a Single Page Application, it's possible to just serve public/index.html to the users.  However, I'll create a simple single page using scaffolding, so that I can take advantage of the asset pipeline.  The command below basically sets up the ability to see a page at <server>/home/show.

rails g controller home show

Since we'll be using ember.js to generate all the content in the page, now is a good time to delete all the contents of app/views/home/show.html.erb.

I'll set that as my application root in config/routes.rb.  This sets up the server to serve app/views/home/show.html.erb when I visit <server>/.

# config/routes.rb
# add this somewhere in the Application.routes.draw block
root :to => 'home#show'

This is probably a good time to delete the public/index.html file.  Because it's a static asset, you may want to investigate somehow using this during production for better performance.  However, since I want to minimize this super-long post, I can eliminate custom heroku deployment steps by deleting it and serving a dynamic rails page instead.

rm public/index.html

I don't need to add the flash & notice helpers in application.html.erb as recommended by the devise generator because that will eventually be handled by ember.

I'll set the option in application.rb to not initialize on precompile:

# config/application.rb
# somewhere in the class defintion
config.assets.initialize_on_precompile = false

And I'll go through config/initializers/devise.rb, changing values where appropriate.  You'll definitely want to change the config.mailer_sender value, and it's worth looking through the options to see what else you want to change.

Now we can use the devise generator to both create our model, and add devise to it.  I'll call the devise-enabled model User.

rails g devise user

Since I want to use token authentication, I'll modify the generated migration for that capability.  You can also disable or enable any other features at this time, or enable them in the future.

# db/migrate/<timestamp>_devise_create_users.rb
      # uncomment the t.string :authentication_token line
      ## Token authenticatable
      t.string :authentication_token
...
      # and the add_index line if you want
      add_index :users, :authentication_token, :unique => true


Migrate the database to create the table...

rake db:migrate

And add :token_authenticatable to the list of devise modules.

# app/models/user.rb
...
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :token_authenticatable
...

Now the tests?

Now we're in a position to start writing tests with guaranteed failures.  That shouldn't be too hard.

First let's set up konacha.  I need to add the gem to my Gemfile.  Also at this time I'll add the poltergeist gem for running these tests headless (aka without a browser), Capybara for rails integration tests, and ruby-debug19 to enable server side debugging for ruby 1.9.x.  If you review the documentation for Capybara, you will see they recommend that when using Test::Unit (as we are) that we should also use the database_cleaner gem.

# Gemfile
...
group :development, :test do
  gem 'database_cleaner'
  gem 'capybara'
  gem 'konacha'
  gem 'poltergeist'
  gem 'ruby-debug19'
end
...

Make sure to run the bundle install command after changing your Gemfile.

bundle install

I'll be creating my tests in test/spec/javascripts.  To use this path with konacha, you've got to create an initializer, and set the spec_dir property in it.  I am also setting poltergeist to the test driver so that later on I can run tests in headless mode. Please see the konacha documentation for general information about this initializer. 

# config/initializers/konacha.rb
if defined?(Konacha)
  require 'capybara/poltergeist'
  Konacha.configure do |config|
    config.spec_dir    = "test/spec/javascripts"
    config.driver      = :poltergeist
    config.stylesheets = %w(application)
  end
end

Without getting too deep into the test implementation details, I'll just say that the following setup is not an ideal konacha setup because DOM content is not in the #konacha div, but it was the only way I could make the version of ember.js I was using work well with konacha.

I'm going to start with two tests.  The first test will just verify that when the sign in page is visited, there are two inputs and a button on the page.  The second test is something like an integration test.  We'll visit the sign in page, fill in the sign in information, then click the "sign in" button.  Before we click the button, we won't be signed in, but we'll be signed in after we click the button.

I've decided to call the sign in/session objects userSession*, because that's the convention that devise uses.

Next to nothing in the following code is implemented right now, but I'll briefly cover the least obvious items after the code.

# test/spec/javasciprts/spec_helper.js.coffee
#= require application
#= require_tree ./support

Konacha.mochaOptions.ignoreLeaks = true


# test/spec/javascripts/userSessionNew_spec.js.coffee
#= require spec_helper 
describe "EmberDeviseExample.userSessionNew", ->

  goToSignIn = ->
    Ember.run ->
      EmberDeviseExample.get('router').transitionTo('signIn')
    
  it "should show sign in page", ->
    goToSignIn()
        
    inputs = window.container.find('input')
    button = window.container.find('button')

    inputs.length.should.equal(2)
    button.length.should.equal(1)


  it "should log in", ->
    goToSignIn()
    
    inputs = window.container.find('input')
    button = window.container.find('button') 
    
    # fill in dummy email & password
    inputs[0].value = 'validuser@example.com'
    inputs[1].value = 'validpassword' 

    EmberDeviseExample.userSession.get('signedIn').should.be.false
    
    # click the button.  We should now be signed in     
    window.server.respondWith([200, {'Content-Type': 'application/json'}, JSON.stringify({token: 'somerandomstring'})])
    button.click()
    window.server.respond()
    EmberDeviseExample.userSession.get('signedIn').should.be.true


The goToSignIn function is a helper that will navigate to the ember.js sign in page. It executes inside an ember runloop to ensure that the action is finalized before the code attempts to perform other ember actions.

The window.container variable will be a handle to an area of the DOM that contains all the form elements.

The window.server variable is a sinon.js mock object.  To use the object, I've copied the sinon.js code to the test/spec/javascripts/support directory, and included the tree in the spec_helper.js.coffee file.  The mock object removes the dependencies of the network and rails application during testing.  We're going to expect that, assuming our rails app returns the given information, our ember.js app will behave as specified in the test.

We can run konacha tests in the console or in the browser.  I'll run them in the browser because IMO it's easier to debug failures in the browser.  The command below starts a web server on port 3500.

rake konacha:serve

In the browser we'll see that the tests complain about the undefined "EmberDeviseExample" variable.  This our cue to introduce ember.js.

Ember.js

I'll be using the ember-rails gem, published by the emberjs team.  Unfortunately it doesn't work out of the box with the jquery-rails gem, because ember-rails provides jQuery 1.8 while ember.js from ember-rails requires jQuery 1.7.  Nevertheless we'll handle that after using the gem to generate a bunch of code for us. Just follow the instructions in the source's README to add it to the project.

# Gemfile
...
gem 'ember-rails'
...

Ensure the gem is installed, then generate the ember bootstrap code.

bundle install
rails g ember:bootstrap -g

I'm going to configure my development environment to use the development variant of ember.js, as recommended by the generator.  I'm also going to configure my test & production environment to use the production variant of ember.js.

# config/environments/development.rb
...
config.ember.variant = :development
...

# config/environments/test.rb
...
config.ember.variant = :production
...

# config/environments/production.rb
...
config.ember.variant = :production
...

And now we'll replace the version of ember.js that came with ember-rails with the github head.  Clone the repo and build it.  As noted in the ember.js README, you will need a javascript runtime to build ember.js.  I installed nodejs and am currently using version 0.8.11.  We're going to copy ember.js from the dist/ folder into our vendor/assets/javascripts folder, which will cause Sprockets to include the vendor/assets/javascripts/ember.js file in application.js, rather than the ember.js file from the ember-rails gem location.

Amazingly you can now start your server, point your browser at localhost:3000, and see the ember.js application.handlebars placeholder content.  We have successfully integrated rails and ember.js!

When we run our konacha tests, we'll see that we've moved beyond the previous error, and now the tests are failing because ember doesn't know what to do when we navigate to the 'signIn' path.

Ember.js objects

To get beyond this test failure, we'll need to add a model, view, controller, and template.  We'll also need to add code in the router that loads the correct controller & view when we visit the 'signIn' path.

Let's start with the template.  This is roughly equivalent to a rails view (.html.erb file).  I love using form builders, and I've found a nice one for ember, ember-forms.  It's very basic right now, but since it has support for buttons, text fields, and password fields, it'll work for us.

Clone the repo, build the source (rake), and copy dist/ember-forms.js to vendor/assets/javascripts.  We also need to add it to application.js just below ember-data.

# app/assets/javascripts/application.js
...
//= require ember-data
//= require ember-forms
...


Now we can create userSessionNew.handlebars in app/assets/javascripts/templates, which will simply output an email input field, a password field, and a submit button.  It's a form that allows the user to create a new user session.

# app/assets/javascripts/templates/userSessionNew.handlebars
{{field email}}
{{field password as="password"}}
{{form buttons submitName="Sign In"}}


Connect it with an ember.js view...

# app/assets/javascripts/views/userSessionNewView.js.coffee
EmberDeviseExample.UserSessionNewView = EF.Form.extend
  templateName: 'userSessionNew'


Create a simple controller for it...

# app/assets/javascripts/controllers/userSessionNewController.js.coffee
EmberDeviseExample.UserSessionNewController = Ember.Controller.extend()


And set up the route.  The ember bootstrap generator created app/assets/javascripts/routes/app_router.js, but like most of the bootstrap files I'm going to convert it to coffeescript.  The last line is where the magic happens.  We will insert an instance of the UserSessionNewView into the outlet of the template associated with the ApplicationController instance. I know that the last sentence used a lot of ember.js terminology and the code uses a lot of conventions so I'll describe them now.

Keep in mind that ember.js objects follow the following convention: if the object begins with a capital letter, it is a prototype (think: class), and if it begins with a lower-case letter, it is an instance.

  • outlet: From the ember.js docs: "an area of a template that has its child template determined at runtime based on user interaction."  The ember:bootstrap generator created one for us in app/assets/javascripts/templates/application.handlebars.
  • ApplicationController instance: Ember.js automatically creates an instance of ApplicationController (I'm not sure when), and names it applicationController.  We get a reference to it using this code: router.get('applicationController').
  • template associated with ApplicationController instance: each ember.js controller has a template associated with it, identified by the value given to the template or templateName properties during controller declaration (*.extend(templateName: ...)).  The ember:bootstrap generator associated the "application" template (app/assets/javascripts/templates/application.handlebars) with the ApplicationController for us in app/assets/javascripts/controllers/application_controller.js.
  • insert an instance of the userSessionNew view: In the code, the parameter to the connectOutlet function is a text string, userSessionNew.  Ember finds the UserSessionNewView, instantiates it, and inserts the instance into the outlet.


# app/assets/javascripts/routes/app_router.js.coffee
EmberDeviseExample.Router = Ember.Router.extend
  location: 'hash'

  root: Ember.Route.extend
    index: Ember.Route.extend
      route: '/'

#      connectOutlets: function(router) {
#        router.get('applicationController').connectOutlet(EmberDeviseExample.ApplicationView);
#      }

      # Layout your routes here...

    signIn: Ember.Route.extend
      route: '/signIn'
      connectOutlets: (router) ->
        router.get('applicationController').connectOutlet('userSessionNew')


When we run our tests, we see that we've made it past the previous error and we need to define the window.container object.  I'm just going to paste what I consider "fixed" content with some generic branding into application.handlebars, and all the site content will be placed where the {{outlet}} is located.  This is where you could add headers, footers, navigation, etc.

# app/assets/javascripts/templates/application.handlebars
<div id="wrapper">
    <h1>TheCompanyLogo</h1>
    {{outlet}}
</div>


We can make window.container point to #wrapper in a konacha beforeEach function.

# test/spec/javasciprts/spec_helper.js.coffee
...
beforeEach ->
  # get the container for the application
  window.container = $('#wrapper')


Rerunning the tests, we have a little success!   We see that the signIn page has the form elements, but we're not yet able to sign in.  That's because we haven't created the userSession object that maintains our signedIn state.  So let's do that.


The UserSession Model

For this interaction with the server, I'm not going to use ember-data.  That's because I probably don't understand ember-data well enough.  I don't think the user session use case fits neatly into an ember-data pattern.  I don't want to make password a property of the user session, and I want to always have a userSession object available, even when the user hasn't logged in.  Please leave comments about integrating userSession with ember-data in the comments.

The general idea is to create an ember.js model that has signIn & signOut methods.  The signIn method, if successful, will set the auth_token property, and set the signedIn property to true.  The signOut method will unset the auth_token property, and set the signedIn property to false.  When either method is unsuccessful, we'll set an errorMsg property that could be propogated to the user.

# app/assets/javascripts/models/userSession.js.coffee
EmberDeviseExample.UserSession = Ember.Object.extend
   auth_token : null,
   urlBase : '/api/v1/tokens',
   errorMsg : null,

   signedIn : (-> 
      return (this.get('auth_token') != null)
   ).property('auth_token')

   signIn : (email, password) ->
      $.ajax 
         url : "#{this.get('urlBase')}.json"
         context : EmberDeviseExample.userSession
         type : 'POST'
         data : 
            email : email
            password : password

         success : (data) ->
            this.set('auth_token', data.token)

         error : (data) ->
            this.errorMsg = "there was an error"
         

      return this.get('auth_token')
   
   signOut : ->
      $.ajax
         url : "#{this.get('urlBase')}/#{this.get('auth_token')}.json"
         context : EmberDeviseExample.userSession
         type : 'DELETE'
         success : (data) ->
            this.set('auth_token', null)
         
         error : (data) -> 
           this.errorMsg = "there was an error"
      return null

EmberDeviseExample.userSession = EmberDeviseExample.UserSession.create()


There are two main items I want to highlight in this code.  First, the signedIn property is computed property based on the presence of an auth_token value.  Second, the very last line creates an instance of the object that I will be able to use whether or not a user has signed in.

Since so much of future testing will involve sinon.js mocking, I'll just start & end mocking in spec_helper.js.coffee beforeEach & afterEach callbacks. 

# test/spec/javascripts/spec_helper.js.coffee (full file)
#= require application
#= require_tree ./support

Konacha.mochaOptions.ignoreLeaks = true

signOut = ->
  window.server.respondWith([200, {'Content-Type': 'application/json'}, JSON.stringify({token: EmberDeviseExample.userSession.get('auth_token')})])
  EmberDeviseExample.userSession.signOut()
  window.server.respond()

beforeEach ->
  # get the container for the application
  window.container = $('#wrapper')

  # set up ajax mocking
  window.server = sinon.fakeServer.create();
  sinon.spy(jQuery, "ajax")


afterEach ->
  # reset application state.  Right not just signing out
  signOut()
    
  # reset the server to use a real server
  window.server.restore()
    
  # unspy
  jQuery.ajax.restore()


Because ember.js will maintain state between tests, we'll need to manually manage state in the afterEach callback.  Right now that just means signing out if we're signed in.

Running our tests, we can see that we're getting closer - we've only failed the final assertion.  That's because there's no code that makes the "Sign In" button click do something.  To pass this test, we'll need to make the form actually do something by handling the save method that was introduced by ember-forms.


Ember.js View and Controller

In ember.js, the typical pattern is to have data in the template (HTML DOM) bound to members of the view.  Events in the view call methods in the controller, which then interacts with the model.  This is example is so simple that those abstraction layers are not necessary, but I'll still use that pattern for consistency with future templates.

In the controller, I'll create a signIn method that accepts email & password data, and calls userSession.signIn method.

# app/assets/javascripts/controllers/userSessionNewController.js.coffee
EmberDeviseExample.UserSessionNewController = Ember.Controller.extend
  signIn: (email, password)->
    EmberDeviseExample.userSession.signIn(email, password)


In the view, ember-forms will call the save member function when a person clicks the "Sign In" button.  The save member takes one parameter, an object whose fields contain data from the form.  In this view, I'll get ahold of the instance of the UserSessionNewController that ember.js created, and call signIn with the form data.

# app/assets/javascripts/views/userSessionNewView.js.coffee
EmberDeviseExample.UserSessionNewView = EF.Form.extend
  templateName: 'userSessionNew'
  save: (data)->
    EmberDeviseExample.get('router').get('userSessionNewController').signIn(data.email, data.password)

All the tests now pass!  Woot.

Final steps

We can't be sure that the rails side of the authentication is working as intended because we haven't set up any tests for it.  So, I'll create a rails integration test that loads the ember.js application, goes to the sign in page, fills out the form, then clicks the "Sign In" button.  That should trigger an ajax call to the test web server, which will query the test database and return data to the ember.js application.  We'll then verify that the ember.js application thinks we're signed in.

Because so many of my rails integration tests will all require that the user be signed in, I'll create a helper for that task in test_helper.rb.  It will just visit the sign in page, fill out the email & password fields, and click the Sign In button.  test_helper.rb will also contain setup code for using Capybara, as described in the Capybara documentation.

# test/test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
  #
  # Note: You'll currently still have to declare fixtures explicitly in integration tests
  # -- they do not yet inherit this setting
  fixtures :all

  # Add more helper methods to be used by all tests here...
end


# Transactional fixtures do not work with Selenium tests, because Capybara
# uses a separate server thread, which the transactions would be hidden
# from. We hence use DatabaseCleaner to truncate our test database.
# DatabaseCleaner.strategy = :truncation, {:except => %w[<table names>]}
class ActionDispatch::IntegrationTest
  # Make the Capybara DSL available in all integration tests
  include Capybara::DSL

  # Stop ActiveRecord from wrapping tests in transactions
  self.use_transactional_fixtures = false

  teardown do
    DatabaseCleaner.clean       # Truncate the database
    Capybara.reset_sessions!    # Forget the (simulated) browser state
    Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver
  end
end

# helpers for logging in & out
class ActionDispatch::IntegrationTest
  def signInEmber(username, password)
    visit('/#/signIn')
    fill_in('email', :with => username)
    fill_in('password', :with => password)
    click_button('Sign In')
  end
end


Note: I couldn't find really good documentation about this, but it appears that Capybara doesn't necessarily wait for ajax calls to complete. To force Capybara to wait for ajax to complete calls, I will add some conditional Handlebars content to the application.handlebars file, and I will assert that the page has the content after sign in. This will cause Capybara to wait for the content to appear.

# app/assets/javascripts/templates/application.handlebars
<div id="wrapper">
    <h1>TheCompanyLogo</h1>
    {{#if EmberDeviseExample.userSession.signedIn}}
        Welcome!
    {{/if}} 
    {{outlet}}
</div>
First we'll test that there are users in the database.  If there are no users in the database, then we'll have a hard time authenticating one, no?  The integration test itself is fairly simple.  We'll sign in, then check the value of EmberDeviseExample.userSession.get('signedIn').

# test/integration/signInEmber_test.rb 
require 'test_helper'

class SignInEmber < ActionDispatch::IntegrationTest
  # load all the to-be-created fixtures
  fixtures :all

  test "users in the db" do
    assert(User.all.size > 0, "there are users in the db")
  end

  test "signInEmber" do
    # use capybara's javascript driver
    Capybara.current_driver = Capybara.javascript_driver

    # we'll need to create a User fixture with email: 'user0@example.com' & password: 'hellohello'
    # this is a helper function for signing in, since so many integration tests will use need to sign in
    signInEmber('user0@example.com', 'hellohello')

    # assert that the ember.js object responsible for keeping track of signIn state thinks we're logged in
    assert(page.has_content?("Welcome"), 'Page has welcome message')
    signedIn = page.evaluate_script('EmberDeviseExample.userSession.get("signedIn")')
    assert(signedIn, "user is signed in")
  end
end


Run the tests...

rake test:integration

The tests fail because because we haven't set up any actual users in fixtures.  Right now we'll just create one user.  I've found that embedded erb statements are the easiest way to set the encrypted_password while also being able to know what is the unencrypted password.

# test/fixtures/users.yml
<% 
  user0 = User.new 
  user0.password = "hellohello"
%>
user0:
  email: user0@example.com
  encrypted_password: <%= user0.encrypted_password %>


Run the tests again:

rake test:integration

We see that now there's users in the db, but the signInEmber test still fails.  That's because we haven't completed the necessary steps for devise token authentication.

While researching how to set up devise token authentication, I found this excellent guide.  We've previously enabled devise's token authenticatable module in the database & rails User model, so we'll just continue to follow the guide at step 2.

Add the rails route.  I'm using the rails scope keyword instead of the namespace keyword because I don't want my rails helpers to be prefixed with the scopes. Either keyword is valid.

# config/routes.rb
  ...
  scope "/api" do
    scope "/v1" do
      resources :tokens, :only => [:create, :destroy]
    end
  end
  ...


We also need to create the tokens controller:

# app/controllers/tokens_controller.rb
class TokensController < ApplicationController
  skip_before_filter :verify_authenticity_token
  respond_to :json
  def create
    email = params[:email]
    password = params[:password]
    if request.format != :json
      render :status => 406, :json => {:message => "The request must be json"}
      return
    end

    if email.nil? or password.nil?
      render :status => 400,
             :json => {:message => "The request must contain the user email and password."}
      return
    end

    invalidMsg = "Invalid email or passoword."

    @user=User.find_by_email(email)
    if @user.nil?
      logger.info("User #{email} failed signin, user cannot be found.")
      render :status => 401, :json => {:message => invalidMsg}
      return
    end

    # http://rdoc.info/github/plataformatec/devise/master/Devise/Models/TokenAuthenticatable
    @user.ensure_authentication_token!

    if not @user.valid_password?(password)
      logger.info("User #{email} failed signin, invalid password")
      render :status => 401, :json => {:message => invalidMsg}
    else
      render :status => 200, :json => {:token => @user.authentication_token}
    end
  end

  def destroy
    @user=User.find_by_authentication_token(params[:id])
    if @user.nil?
      logger.info('Token not found.')
      render :status=>404, :json=>{:message=> 'Invalid token.'}
    else
      @user.reset_authentication_token!
      render :status=>200, :json=>{:token=>params[:id]}
    end
  end
end


And that's it!  The devise authentication token can be retrieved using EmberDeviseExample.userSession.get('auth_token').

In the next article I'll describe how to use jQuery.ajaxSetup to append the authentication token to all future requests.

As previously mentioned, a copy of the code in this state is available here: https://github.com/avitevet/ember_devise_example, under the tag "blog_1." Please leave questions and comments, I'd love to hear what I can improve!

0 Responses to "Ember + Rails + devise token authentication, first step working together in harmony"