I searched for a long time trying to find info on how to secure an API with OAuth. All I really found were ways to do it using the 3-leg approach. There are good tutorials out there on how to do that. Check our doorkeeper and railscasts.com.

I wanted something simpler. I found a great post here and noticed in the comments that what the author describes is basically two-legged OAuth. What the article lacks is how to implement it. So here goes:

Goals:

  1. I want have a system where users can create an account.
  2. With each account the user will get an API Key, and a Secret.
  3. Each request to the API, will encrypt the params with the secret, and pass the hash along with the Key to identify the user. The server will take the same params(since they are passed in) and use the users secret to encrypt them, and then check to see if the result is the same. If so, the user is legit and can use the API.

Flow:

  1. User creates account
  2. User goes to account page and writes down API Key and Secret
  3. Using API and Secret, user makes calls to the API

Creating the API Application

  1. rails new awesome_api
  2. For this app I am using devise for authentication
  3. gem ‘devise’ in Gemfile, bundle install, rails g devise:install
  4. rails g devise User
  5. rails g devise:views
  6. add two fields to the devise migrations for users
  7. t.string :api_key
    t.string :secret
  8. rake db:migrate
  9. create a welcome controller, rails g controller welcome index
  10. uncomment “root :to…” in routes.rb

Add code to user model

before_validation :generate_keys, :on => :create

protected

  def generate_keys
    self.api_key = OAuth::Helper.generate_key(40)[0,40]
    self.secret = OAuth::Helper.generate_key(40)[0,40]
  end

note: you probably want to do a loop around the generate keys calls to make sure they are unique.
Update views/devise/registrations/edit so you can see the api key and secret.

<h3>Api Keys</h3>
<b>Api Key:</b> <%= resource.api_key %>
<br/>
<b>Secret:</b> <%= resource.secret %>
  • create a base controller for our API, rails g controller api_base

Add this code to the ApiBase controller

require 'oauth/request_proxy/rack_request'

class ApiBaseController < ApplicationController

  before_filter :run_oauth_check

  protected

  def run_oauth_check
    req = OAuth::RequestProxy::RackRequest.new(request)
    return render :json => { :error => "Invalid request" },
        :status => 400 unless req.parameters['oauth_consumer_key']

    client = User.find_by_api_key req.parameters['oauth_consumer_key']
    return render :json => { :error => "Invalid credentials" },
        :status => 401 unless client != nil

    begin
      signature = ::OAuth::Signature.build(::Rack::Request.new(env)) do |rp|
        [nil, client.secret]
      end

      return render :json => { :error => "Invalid credentials" },
          :status => 401 unless signature.verify
    rescue ::OAuth::Signature::UnknownSignatureMethod => e
      return render :json => { :error => "Unknown signature method" }, :status => 400
    end
  end

end
  • now, lets create our first api controller
  • rails g controller api info

Update the ApiController.rb file

class ApiController < ApiBaseController
  def info
    @data = { "coincoin" => "o< o<" }

    respond_to do |format|
      format.json { render :json => @data }
    end
  end
end

run rails s and create a user account, go to the user edit page and grab the api key and secret.

Create The Client

This code can be put into a separate library or even just a controller of another app. This example was taken from a controller in a new app to call the api.

require "oauth"
@consumer=OAuth::Consumer.new( "API-KEY","SECRET", {
    :site=>"http://localhost:3000",:http_method => :get
    })
    @accessToken = OAuth::AccessToken.new(@consumer)
    @body = @accessToken.get("/api/info").body rescue ""

Create an account from the producer app and grab the key and secret from the edit user page.
Change the site to fit your environment.
The @body variable will have json for you to parse. You can now add all the api functionality you want, just inherit from ApiBaseController and it will secure all calls!

Good Luck!, feel free to comment if you have issues, I will do my best to help out.

Update: I forgot to mention, make sure to do everything over SSL in a production environment.