February 07, 2015

Update Nov. 29/15: Brand new and updated tutorial found here!

Having gotten into Ember lately, I decided to write this article to help beginners set up a Ruby on Rails back-end to go with an Ember front-end.

I’ll assume you have Ruby, Rails, Ubuntu set up (I’m using 2.1.2, 4.2, and 14.04 respectively) and are already familiar with Devise.

First, let’s install node and npm. I prefer to use Chris Lea’s repository. (Update March 15/15: consider using nvm)

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs
sudo ln -s /usr/bin/nodejs /usr/bin/node

Go ahead and install Ember-CLI and Bower

sudo npm install -g ember-cli bower

Install watchman as well since Ember-CLI uses it to watch for file changes.

Once that’s done, let’s start our project!

mkdir ember-rails-sample
cd ember-rails-sample

Since I like to keep Ember-CLI and Rails in separate directories, this will be simple.

rails new sample_back
ember new sample_front

Rails

Let’s start with the back-end first. Include Devise in your Gemfile

...
gem 'devise'
...

and run

bundle install
rails g devise:install
rails g devise User

We’ll need to add an additional column to the Users table to hold the authentication token needed to verify a user when an API call is made. Open the migration that Devise generated (or create a separate one), and add the following line

t.string :authentication_token, null: false, default: ""

Then, open app/models/user.rb and change it to this

class User < ActiveRecord::Base
  before_save :set_auth_token
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  private
  def set_auth_token
    if self.authentication_token.blank?
      self.authentication_token = generate_authentication_token
    end
  end

  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
end

This will ensure that an authentication token is generated for each user in the database.

Now, let’s start modifying the routes. If you open config/routes.rb, you’ll notice Devise already inserted

devise_for :users

We’re going to override Devise’s default functionality, so let’s tell it to use our own controller. For this article, we’re only going to be implementing sessions (i.e. logging in/out). I’ll leave it as an exercise to the reader to implement registration (i.e. creating a new account). For now, we’ll just use the Rails console to create accounts for our purposes.

devise_for :users, controllers: { sessions: 'sessions' }

Then we’ll create our controller

rails g controller Sessions

In app/controllers/sessions_controller.rb, we’ll make it inherit from Devise::SessionsController and we’ll override the create method.

class SessionsController < Devise::SessionsController
  def create
    respond_to do |format|
      format.json do
        self.resource = warden.authenticate!(auth_options)
        sign_in(resource_name, resource)
        data = {
          token: self.resource.authentication_token,
          email: self.resource.email
        }
        render json: data, status: 201
      end
    end
  end
end

The above code will authenticate the user and send back the authentication token as well as the E-mail.

Let’s say there are controllers that you want only authenticated users to access. To do so, place this at the top of such controllers

before_filter :authenticate_user_from_token!

Now, we’ll define authenticate_user_from_token in application_controller.rb (so that all controllers can inherit this method). Open the file and replace it with the following code:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session

  protected
  
  def authenticate_user_from_token!
    authenticated = authenticate_with_http_token do |user_token, options|
        user_email = options[:user_email].presence
        user       = user_email && User.find_by_email(user_email)

        if user && Devise.secure_compare(user.authentication_token, user_token)
          sign_in user, store: false
        else
          render json: 'Invalid authorization.'
        end
      end

    if !authenticated
      render json: 'No authorization provided.'
    end
  end
end

You may have also noticed that I set

protect_from_forgery with: :null_session

instead of :exception. This is because we’re building a stateless JSON API which requires an authentication token to access confidential data (which we’ve just implemented), thus we’ll omit the use of Rails’ CSRF token checking (it’ll just set an empty session when the CSRF is missing). Furthermore, open config/initializers/session_store.rb and change the session store to disabled

Rails.application.config.session_store :disabled

For reasons explained here. Make sure you do this!!!

Let’s create a user before moving on to Ember. Open db/seeds.rb and create a user to login with

if !User.any?
  User.create(email: '[email protected]', password: 'password')
end

then run

rake db:migrate db:seed

Ember

I’m currently using Ember 1.8.1 and Ember-CLI 0.1.12

npm install --save-dev ember-cli-simple-auth ember-cli-simple-auth-devise

This will install install the modules globally (-g) and will save them as dependencies in package.json (–save-dev).

Once that’s done, open config/environment.js. Right after ENV is defined, add the following

ENV['simple-auth'] = {
  authorizer: 'simple-auth-authorizer:devise'
}

ENV['simple-auth-devise'] = {
  tokenAttributeName: 'token',
  identificationAttributeName: 'email'
}

The first line tells simple-auth to use the Devise authorizer.

The second line tells the Devise authorizer to use ‘token’ and ‘email’ as the keys to send to the server when performing authorization. Normally, you wouldn’t specify identificationAttributeName, but since I’m using ember-simple-auth 0.7.3 at the time of this writing, and according to the changelog, we have to specify identificationAttributeName to be ‘email’, otherwise it’ll send ‘user_email’. Our server-side authentication expects ‘email’, so if you don’t specify it, you might get responses such as “You need to sign in or sign up before continuing” when trying to log in.

Now we’ll add a way to allow users to actually log in. Start by issuing the following commands

ember g route login
ember g controller login

This will add this.route("login"); to app/router.js and will create a login route, controller, their unit tests and a template.

We need to alter the login controller to make it use the functionality provided by simple-auth. To accomplish this, we’ll use a mixin from simple-auth. Your app/controllers/login.js should look like this

import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';

export default Ember.Controller.extend(LoginControllerMixin, {
  authenticator: 'simple-auth-authenticator:devise'
});

Furthermore, open app/routes/login.js and change it to this

import Ember from 'ember';
import UnauthenticatedRouteMixin from 'simple-auth/mixins/unauthenticated-route-mixin';

export default Ember.Route.extend(UnauthenticatedRouteMixin);

The UnauthenticatedRouteMixin mixin can be used to redirect a user away from a route that they shouldn’t access if they’re already logged in (such as the login route). Similarly, you can use the AuthenticatedRouteMixin to navigate users away from routes that they should only access if they’re logged in. For more info, read the docs here.

Now we can begin modifying the templates! ember-cli-simple-auth provides the session object which allows us to customize our templates. Open app/templates/application.hbs insert this

<h2 id="title">Welcome to Ember.js - With Authentication!</h2>

{{#if session.isAuthenticated}}
  Hi, {{session.email}}
  <br/>
  You can't see this text unless you're logged in!
  <br/>
  Click this button to logout: 
  <button {{action 'invalidateSession'}}>Logout</button>
  <br/>
  If you try to go to the
  {{#link-to "login"}}Login{{/link-to}}
  page, you should get redirected!
{{else}}
  {{#link-to "login"}}Login{{/link-to}}
{{/if}}

{{outlet}}

We’ll be able to go to the Login page, but it’s currently empty. Open app/templates/login.hbs and paste this in

<form {{action "authenticate" on="submit"}}>
  <div>
    <label for="identification">E-mail</label><br />
    {{input value=identification placeholder="E-mail" type="text" name="email"}}
  </div>

  <div>
    <label for="password">Password</label><br />
    {{input value=password placeholder="Password" type="password" name="password"}}
  </div>

  <div>
    <button type="submit">Log in</button>
  </div>
</form>

Now we’re ready to launch. Open two terminals and navigate one to the Rails app, and the other to the Ember-CLI app and run

rails s

Wait for it to load, then run

ember s --proxy http://localhost:3000

Open a web browser and navigate to localhost:4200. You should see the homepage we made along with a login button. Try logging in with the test user we created above. If all goes well, you should see the text we pasted in the authenticated section of application.hbs.

blog comments powered by Disqus