For a recent Ember.js + Rails API project, I needed to set up OAuth with support for GitHub. I used the Torii library along with EmberCLI. This a rough, not quite finished version.

I’m using

  • EmberCLI v.1.13.8
  • Ember.js v.2.1.0
  • Torii v.0.6.1
  • Ruby on Rails API v.5

Before you get started

Make sure your app is registered on your GitHub account.

Go to Settings > Applications > Developer Applications

To get things up and running in development, just set Homepage URL and Authorization callback URL to http://localhost:4200

Ember.js

I want to authenticate users who try to visit a page of shared links. Any code references to links can be interchanged with whichever model you are using in your project.

Add Torii to your package.json

// package.json
"torii": "^0.6.1"

Configure torii in your ENV variable

// config/environment.js
module.exports = function(environment) {
  var ENV = {
    torii: {
         sessionServiceName: 'session',
         providers: {
           'github-oauth2': {
             //Key GitHub gives you once you register your app
             apiKey: 'API_KEY_FROM_GITHUB',
             //scope for your authentication
             scope: 'user',
             //This is the link that it redirects you back to
             redirectURI: '/links'
           }
         }
       }
   ;}
 };

Create a new folder called torii-adapters

// app/torii-adapters/application.js
export default Ember.Object.extend({
  open: function(authentication){
    var authorizationCode = authentication.authorizationCode;
    localStorage.token = authorizationCode;
    return new Ember.RSVP.Promise(function(resolve, reject){
      Ember.$.ajax({
        // url on your server for storing your session info
        url: 'http://localhost:3000/session',
        data: { 'access_code': authorizationCode },
        dataType: 'json',
        success: Ember.run.bind(null, resolve),
        error: Ember.run.bind(null, reject)
      });
    }).then(function(user){
      // The returned object is merged onto the session (basically). Here
      // you may also want to persist the new session with cookies or via
      // localStorage.
      return {
        currentUser: user.user
      };
    });
  },
  fetch: function() {
    if (!localStorage.token) {
      return rejectPromise();
    }
    return new Ember.RSVP.Promise(function(resolve, reject){
      Ember.$.ajax({
        url: 'http://localhost:3000/session/fetch',
        data: { 'access_code': localStorage.token },
        dataType: 'json',
        success: Ember.run.bind(null, resolve),
        error: Ember.run.bind(null, reject)
      });
    }).then(function(user){
      // The returned object is merged onto the session (basically). Here
      // you may also want to persist the new session with cookies or via
      // localStorage.
      return {
        currentUser: user.user
      };
    });
  }
});

Add a login route to your router.js file

// app/router.js
Router.map(function(){
  this.route('login');
});
export default Router;

You need to create the before_model and logout hooks in your application route

// app/routes/application.js
export default Ember.Route.extend({
  model: function(){
    return this.store.findAll('link');
  },

  beforeModel: function() {

    return this.get('session').fetch().then(function() {
      console.log('session fetched');
    }, function() {
      console.log('no session to fetch');
    });
  },

  actions: {
    logout: function() {
      this.get('session').close();
    }
  }
});

Create a new route for login

// app/routes/login.js
export default Ember.Route.extend({
  actions: {
    signInViaGithub: function(){
      var route = this,
          controller = this.controllerFor('login');
      // The provider name is passed to `open`

      this.get('session').open('github-oauth2').then(function(){
        route.transitionTo('links');
      }, function(error){
        controller.set('error', 'Could not sign you in: '+error.message);
      });
    }
  }
});

Create a new login controller

// app/controllers/login.js
import Ember from 'ember';

export default Ember.Controller.extend({

});

Template for logging in

{{! app/templates/login.hbs}}
{{#if session.isWorking}}
  One sec while we get you signed in...
{{else}}
  {{error}}
  <a href="#" {{action 'signInViaGithub'}}>
    Sign In with Github
  </a>
{{/if}}

Login page

// app/routes/link.js
import Ember from 'ember';

export default Ember.Route.extend({
  model: function(){
    return this.store.findAll('link');
  }
});

Toriii has a helper authenticatedRoute. I set it up for my links pages in my router.js file.

// app/router.js
Router.map(function() {
  this.authenticatedRoute('links', function(){
    this.route('new');
  });

  this.route('login');

});

export default Router;

Rails 5 API setup

I setup a User model in my Rails application. Here’s what my migration file looks like

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :login
      t.string :avatar_url
      t.string :github_url
      t.string :name
      t.string :company
      t.string :location
      t.string :email
      t.text :bio
      t.string :code
      t.timestamps
    end
  end
end

As I mentioned before, I’m using a Link feature. I needed three different urls to keep track of the sessions. This is my routes.rb file

Rails.application.routes.draw do
  resources :links
  resources :users

  get "/session", to: "session#index"
  get "/session/fetch", to: "session#fetch"
  post "/session/logout", to: "session#logout"

end

To keep my sessions controller “skinny”, I extracted my GitHub API logic into an interactor. After that, this is my sessions_controller.rb

class SessionController < ApplicationController

  def index
    user = GithubApi.new(params[:access_code]).get_user
    render json: user
  end

  def fetch
    user = User.find_by(code: params[:access_code])
    render json: user
  end

  def logout
    user = User.find_by(code: params[:access_code])
    user.code = "0"
  end

end

With that, you should have all the pieces you need to get basic OAuth2 functionality with GitHub.