Skip to content

Simple Two-Legged OAuth Provider in Rails

I’ve spent the past 24 hours searching, reading and hacking my way to a very simple implementation of a Ruby on Rails based two-legged OAuth provider.  I now have a working solution and I thought I’d share seeing as I was unable to find a solid resource on this for a Rails site.

A quick disclaimer: this hasn’t been tested extensively, though I am pretty confident that this will get you off the ground for a basic set of GET/POST/PUT/DELETE requests sending along form data (Content-Type: application/x-www-form-urlencoded for PUT/POST requests).

A Bit About OAuth

Now, I’m not going to dive into the low-level details of how two-legged works, but I feel it is important to know what OAuth is trying to accomplish and what the two-legged version is doing.  OAuth is an open standard that was developed so that websites with APIs (ie Twitter) could allow users to securely grant third party websites access to their data.  OAuth accomplishes this without ever giving the user’s password to the third party.  Thus, you can authorize a specific client (TweetDeck) access to a specific user’s account information. The entire process is accomplished with a sequence of access/authorization tokens that are issued to the third party only when the end user explicitly grants access to the party.  This is what is happening when you authorize a website access to your Twitter account data.  So, traditional OAuth (aka three-legged OAuth) was created to securely grant third parties access to user data.  Two-legged OAuth, while similar to three-legged, does not control access to specific user data.  This means that you can control access on a per-client basis, but the protocol doesn’t give you user data access control.

As I said, the entire process is build upon the exchange of access keys.  Two-legged OAuth requires two keys need to be known by both the client and the server.  The first key, the public key, is transported publicly in the HTTP request and used as a client identifier.  The second key, called the secret key, is known by both the client and server but is never transported in the HTTP request.  The client uses this secret key to hash the request (which is then added to the request as a parameter) before sending it to the server. Upon receiving the request, the API looks up the client’s secret key based on the public key provided and then hashes the request with that secret key.  If the hashes match, the client is authenticated and the request is allowed.  The key here is that only the server and client know the secret key, ensuring that only the client can sign requests that the server will accept.

Note that this method only gives you a secure method to authenticate clients, the data in the request is still transported in plain text.

The Code

We’re going to use the Ruby OAuth gem to streamline this process.  The gem provides the basic request hashing functionality, so all we’ll have to do is integrate this with Rails.  To install this, simply insert one line into your Gemfile and run a bundle install:

gem 'oauth'

To get this working our Rails app needs to store API public key/secret key pairs so that we can authenticate our clients.  We’ll store the key pairs in our database, allowing a simple lookup method and the ability to store meta data about our API clients if needed.  Using this setup we could implement throttling and custom access controls, but these topics are beyond the scope of this post.  Lets first write our database migration to get the table setup; I’m going to keep this incredibly simple for the purposes of this post.

class Client < ActiveRecord::Migration
  def change
    create_table :client do |t|
      t.string :api_key
      t.string :secret

      t.timestamps
    end
  end
end

Great, run that migration to get the table into your database and we’ll move on to performing the authentication.  In order to accomplish this we’re going to add a protected method to the ApplicationController and run before processing each request using a before_filter

require 'rack'
require 'rack/request'
require 'oauth'

class ApplicationController < ActionController::Base     
  before_filter :run_oauth_check          
  # ...           

  protected           
  def run_oauth_check                 
    render :json => { :error => "Invalid request" },
        :status => 400 unless params[:oauth_consumer_key]

    client = Client.find_by_api_key params[:oauth_consumer_key]
    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

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

Let’s take a look at what is happening here. First, we check to make sure that oauth_consumer_key is specified in the request. This is a parameter required by the OAuth spec that contains our client’s public API key. Next, we pull the client’s data from our database so we can verify the signature with their secret key. If no client is found with the provided API key, we reject the request. Once we’ve done that, all we have to do is use the OAuth gem to build the signature. This is done by first instantiating a new Rack request object and providing it to the signature builder. Finally, we have to account for the situation where the client didn’t provide their signature method in the request with a rescue block (the OAuth spec has more information about the different request signing methods – basically they’re different hash functions).

That’s it!  Requests signed with incorrect credentials will now be rejected, and authorized requests will be allowed through.  The important takeaway from this example is the signature verification process.  I’ve included everything else as an example of its use, but so long as you can build the Rack request as we did above, you can implement this code in many different contexts.

Philip Southam has a great post on signing the requests in both Python and Ruby here. The post also contains a tutorial for this same setup in Python.

 

Categories: Programming, Ruby on Rails.

Tags: ,

Comment Feed

9 Responses

  1. Thank you, thank you, thank you for finally making this easy to do. I needed a few changes to get it working with Rails 3 but they were minimal and this has been a light at the end of a long confusing tunnel.

    • Phil, can you help me out? I’m trying to implement it.

      If you can, add my facebook.com/ricardonacif

      Thanks!

      • I am genuinely grateful to the owner of tbis siote whho has
        shared this enormous piece of writing at here.

      • Simply desire to say your article is as amazing. The clarity for your post is simply great and that
        i can assume you’re knowledgeable on this subject. Well with your
        permission allow me to take hold of your RSS feed to stay up
        to date with coming near near post. Thanks one million and please keep up the
        rewarding work.

      • Very good blog! Do you have any tips and hints for
        aspiring writers? I’m hoping to start my own website soon but
        I’m a little lost on everything. Would you advise starting with a free
        platform like WordPress or go for a paid option? There are so many options out there that I’m totally
        confused .. Any suggestions? Many thanks!

      • Nice blog right here! Also your site lots up very fast!
        What web host are you the usage of? Can I get your affiliate link on your host?
        I want my website loaded up as fast as yours lol

      • Hi, this weekend is good in support of me, for the reason that
        this time i am reading this enormous informative paragraph
        here at my house.

    • Thank you a bunch for sharing this with all people you
      really know what you are talking approximately!
      Bookmarked. Kindly also consult with my site =). We can have a hyperlink alternate contract between us

  2. @Ricardo – I don’t have Facebook, but hopefully this gist will help you out: https://gist.github.com/4661334



Some HTML is OK

or, reply to this post via trackback.