Appalling Farrago

A blog about stuff

EmberCLI, Torii and Rails

07 Dec 2014

This is the second part to my previous post.

Rails

When we left off we had a ember app setup on divshot to talk to a rails application for authentication via github. We’ll start by generating a new rails app:

rails new jibber
cd jibber
bundle

The first thing we’ll want to do is create the route that our ember application is posting the github temporary token to:

# config/routes.rb
Rails.application.routes.draw do
  namespace :v1 do
    resources :sessions, only: [:create]
  end
end

Then we’ll make our sessions controller:

# app/controller/v1/sessions_controller.rb
module V1
  class SessionsController < ApplicationController
    skip_before_action :verify_authenticity_token

    def create
      github_authenticator = GithubAuthenticator.new(github_auth_code)
      user_factory = UserFactory.new(github_authenticator)
      user = user_factory.find_or_create_user

      render json: user, status: :created
    end

    private

    def github_auth_code
      params.require(:'github-auth-code')
    end
  end
end

We have to skip the verify_authenticity_token before_action which is used to ensure requests are coming from the same origin. As our ember application will be on a different domain than our rails application, we need this check to be skipped.

The create action is a great example of writing code I wish I had. The first line creates a new instance of the GithubAuthenticator class. This class should be passed the github auth token. The second line passes the instance of GithubAuthenticator to instantiate another class that doesn’t yet exist, the UserFactory. We then call find_or_create_user on the user_factory, which should return a user, and pass the result to be rendered as json with a status of :created. We always pass :created as the status as we’re letting the browser know that a new session, not a user, has been created.

Let’s go ahead and make the UserFactory class so we can spec out what the GithubAuthenticator needs to provide:

# app/services/user_factory.rb
class UserFactory
  def initialize(authenticator)
    @authenticator = authenticator
  end

  def find_or_create_user
    User.find_or_create_by(name: authenticator.name)
  end

  private

  attr_reader :authenticator
end

So we pass an authenticator to the UserFactory’s initialize. The find_or_create_user then finds or creates the user using a name provided by the authenticator. There’s nothing here specific to github, so adding extra sign in services is just a case of creating other authenticator classes that implement a name method.

Now let’s look at the GithubAuthenticator class:

# app/services/github_authenticator.rb
require "net/http"
require "net/https"

class GithubAuthenticator
  GITHUB_OAUTH_PATH = "https://github.com/login/oauth/access_token"

  def initialize(auth_code)
    @auth_code = auth_code
  end

  def name
    github_user[:login]
  end

  private

  def github_user
    @github_user ||= github_client.user
  end

  def github_client
    Octokit::Client.new(access_token: access_token)
  end

  def access_token
    github_response["access_token"]
  end

  def token_type
    github_response["token_type"]
  end

  def scope
    github_response["scope"]
  end

  def github_response
    @github_response ||= JSON.parse(res.body)
  end

  def res
    http.request(req)
  end

  def req
    req = Net::HTTP::Post.new(uri.path)
    req.set_form_data(post_data)
    req["Accept"] = "application/json"
    req
  end

  def http
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http
  end

  def uri
    URI.parse(GITHUB_OAUTH_PATH)
  end

  def post_data
    {
      "client_id" => ENV["GITHUB_KEY"],
      "client_secret" => ENV["GITHUB_SECRET"],
      "code" => @auth_code
    }
  end
end

This class is pretty long, and I’d consider extracting a class to handle the oauth post to github. I won’t go over every line, as I hope the code is clear, but here’s an overview. We initialize with the temporary token provided by github and make a Net::HTTP request following the guidelines provided by github. This returns an oauth token. We use Octokit to request the user from github and extract the :login for our name method.

We’ll need to add gem "octokit" to our gemfile and run bundle. I also use dotenv to manage environment variables in development so go ahead and add that to dev and test and put your github authentication details into .env:

# .env
GITHUB_KEY=github_develoment_key
GITHUB_SECRET=github_development_secret

Finally we’ll want to create a User model and a migration:

# app/models/user.rb
class User < ActiveRecord::Base
  validates :name, presence: true, uniqueness: true

  before_create :generate_token

  protected

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64(nil, false)
      break random_token unless User.exists?(token: random_token)
    end
  end
end
# db/migrate/20141204213638_create_user.rb
class CreateUser < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :token

      t.timestamps
    end
  end
end

The user class is simple enough, we generate a unique token on create that the ember app can use for retrieving a session.

Now if we have everything wired up correctly, with our rails app running:

rails s -p9000

and our ember-cli app running:

ember s --proxy=http://localhost:9000

we should be able to visit localhost:4200 and click on our login button. We then see our success alert and can see in the console the rails app returning our user:

jibber-auth-success

Now lets get this up on heroku.

Heroku

Get yourself an account on heroku and install the toolbelt with brew install heroku-toolbelt. Once you’re setup, go ahead and create your heroku app. You’ll then want to commit all our changes to git (if you haven’t already) and push your code up to heroku:

heroku create jibber-rails
git add -A .
git commit
git push heroku master
heroku run rake db:migrate

We’ll also want to set our two environment variables for github credentials:

heroku config:set GITHUB_KEY=github_production_key GITHUB_SECRET=github_production_secret

That gets us the same functionality in production!

Up Next

Next time we’ll setup creating a session on the ember side and allowing the ember app to retrieve the session using the user’s token.

comments powered by Disqus