dagi3d.net v5.0

API authentication

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

Paperclip::FaceCrop

On a recent project I was working on, we needed to collect a bunch of images and generate the thumbnails for them automatically with Paperclip. The problem we found was that on most of the collected pictures people did appear on them and their heads where cut out off the photo on the generated thumbnails because Paperclip uses the center of the image to calculate the new area.

In order to solve this issue, I came with the idea of writing a Paperclip processor to try to recognize all the faces located in the picture, calculate a surrounding area and generate the thumbnail with the desired width and height by using this new area as the center point. The faces are found by using the OpenCV library so it only has to calculate the new area and write the thumbnail image.

Original image:
Paperclip::Thumbnail processor
class Image < ActiveRecord::Base
  has_attached_file :attachment,
      :styles => {:thumbnail => "200x125"}
end

Paperclip::Facecrop processor:
class Image < ActiveRecord::Base
  has_attached_file :attachment,
      :styles => {:thumbnail => "200x125"},
      :processors => [:face_crop]
end

The solution is far from being perfect because sometimes the faces aren't recognized but I guess it could generate more accurate results by using more trained filters sets

Source code and usage instructions can be found at github: [https://github.com/dagi3d/paperclip-facecrop]