I have started developing an API service for an application I'm working on and one of the requisites is that all the requests need to be authenticated through the server in order to control the applications that access to it. I did some research to learn which mechanisms are using other services to authorize third party applications and faced there are plenty of them, like for example, OAuth, basic digest or sending the auth tokens inside the request headers - one interesting project I think is worth to have a look at is Fog, a ruby library that provides a common interface to access to different cloud computing providers.

Pat Allan recently wrote a very well explained article about how to implement the authentication through the headers. The only problem I see with this approach is that the application tokens are exposed in plain text inside the request and could be read by a sniffer, so using SSL would be recommended. In case you can't or don't want to use a secure connection at the endpoint, you can always add some extra security layers without much effort like sending a signed header in the request(by the way, no solution is 100% perfect):

Say we have the following spec:

users_controller_spec.rb
require File.expand_path('../../spec_helper', __FILE__)

describe UsersController do

  let(:api_key) { "myapp" }
  let(:api_token) { "12345" }
  let(:app) { mock(Application, :api_key => api_key, :api_token => api_token) }

  def signature(key, data)
    digest = OpenSSL::Digest::Digest.new('sha256')
    Base64.encode64(OpenSSL::HMAC.digest(digest, key, data)).chomp
  end

  it "returns a 403 http code when authentication fails" do
    get :index, :format => :json
    response.response_code.should == 403
  end

  it "authenticates the requests through the headers" do
    Application.stub(:find_by_api_key).and_return(app)

    now = Time.now.rfc2822
    request.env['X-Auth-ApiKey'] = api_key
    request.env['X-Auth-Signature'] = signature(api_token, now)
    request.env['X-Auth-Date'] = now

    get :index, :format => :json
    response.response_code.should == 200
  end

end

What we are doing here is to use the api token as the key to encode a text, in this case, the current date and time. Then, in our controller we will obtain the registered application with the given api key, get its token and sign the value given in the X-Auth-Date header using the same algorithm we used on the client. If the value set in X-Auth-Signature is equal to the new signed value, the access will be granted:

class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :authorize

  private
  def authorize
    api_key = request.headers['X-Auth-ApiKey']
    signature = request.headers['X-Auth-Signature']
    date = request.headers['X-Auth-Date']

    app = Application.find_by_api_key(api_key)

    render :text => "Forbidden access", :status => 403 unless app && 
        signature == sign_request(app.api_token, date)
  end

  def sign_request(api_token, date)
    digest = OpenSSL::Digest::Digest.new('sha256')
    Base64.encode64(OpenSSL::HMAC.digest(digest, api_token, date)).chomp
  end
end

note: the headers names are totally arbitrary

This approach has the problem that a valid signature could get stolen and be used by unauthorized users on successive requests. One possible solution could be to set an expiration time limit and use the passed date to control it:

it "denies access to requests more than five minutes old" do
  Application.stub(:find_by_api_key).and_return(app)

  now = (Time.now - 5.minutes - 1.second).rfc2822

  request.env['X-Auth-ApiKey'] = api_key
  request.env['X-Auth-Signature'] = signature(api_token, now)
  request.env['X-Auth-Date'] = now

  get :index, :format => :json
  response.response_code.should == 403
end
class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :authorize

  private
  def authorize
    api_key = request.headers['X-Auth-ApiKey']
    signature = request.headers['X-Auth-Signature']
    date = request.headers['X-Auth-Date']

    app = Application.find_by_api_key(api_key)

    render :text => "Forbidden access", :status => 403 unless app &&
        valid_date(date) &&
        signature == sign_request(app.api_token, date)
  end


  def sign_request(api_token, date)
    digest = OpenSSL::Digest::Digest.new('sha256')
    Base64.encode64(OpenSSL::HMAC.digest(digest, api_token, date)).chomp
  end

  def valid_date(request_date)
    now = Time.now
    request_date = Time.parse(request_date)
    expiration_date = now - 5.minutes

    request_date >= expiration_date && request_date <= now
  end

end

Eventually, we could go even further and check that the same signature isn't used twice within the allowed time frame maybe by saving them in a key-value datastore like Redis, which allows to set their own timeouts so that they expire automatically: http://redis.io/commands/expire