hmac-auth-rails

Disclaimer: Although this is covered in the MIT license agreement, please be aware that in no way will the authors be responsible for any security issues or loss of income that may arise from using this code. Use this code at your own risk.

Overview

This application integrates with Rails and provides in-built SHA1 HMAC authentication. It was originally built to be used with angular-hmac-auth an open-source Angular module. Currently it only supports SHA1 encryption. If you require better encryption methods please contribute.

Installation

Install the Gemfile

Add the following line to your Rails Gemfile:

gem 'hmac-auth-rails'

Make your authentication model authenticatable

Add the line below to your applications model that handles the authentication:

class User < ActiveRecord::Base

  hmac_authenticatable

Built-in authentication fields

To use built-in fields you will need to ensure that your authentication model has the auth_token and secret_key string fields defined:

class User < ActiveRecord::Base

  hmac_authenticatable

  field :auth_token, type: String
  field :secret_key, type: String

Custom authentication fields

If you do not wish to use the built-in fields you can specifiy your own custom fields in the model:

class User < ActiveRecord::Base

  hmac_authenticatable

  self.auth_token_field = :custom_auth_token
  self.secret_key_field = :custom_secret_key

  field :custom_auth_token, type: String
  field : custom_secret_key, type: String

Auto-generating auth tokens and secret keys

The following snippet can help you in setting up your model to auto-generate the auth_token and secret_key when a new record is initialised.

  after_initialize :generate_key, :generate_token

  protected
  def generate_token
    self.auth_token ||= loop do
      random_token = SecureRandom.hex(32)
      break random_token unless User.exists?(auth_token: random_token)
    end
  end

  def generate_key
    self.secret_key  ||= SecureRandom.base64(64)
  end

When using devise

To ensure that you validate HMAC authentication before the :authenticate_user! method is called. You will want the following line.

prepend_before_filter :hmac_auth

To exclude certain actions from being hmac_authenticatable you could use the following exclude parameter.

prepend_before_filter :hmac_auth, :except => :my_action

Applying HMAC authentication globaly

A good way to apply HMAC authentication globaly is to add :hmac_auth to your ApplicationController and inherit the controller for all other sub-controllers.

module Api::V1
  class ApiController < ApplicationController
    prepend_before_filter :hmac_auth
  end
end

Your sub-controller would look similar to the following:

module Api::V1
  class UsersController < ApiController
  end
end

Testing with Rspec

Set into the spec/dummy folder and run the following:

bundle exec rspec

Plays nice with

todopagos/angular-hmac-auth

https://github.com/todopagos/angular-hmac-auth

plataformatec/devise

https://github.com/plataformatec/devise

Putting all the "plays nice with" together

Create an API Application Controller that excludes the auth action:

module Api::V1
  class ApiController < ApplicationController
    prepend_before_filter :hmac_auth, :except => :auth
  end
end

Setup the auth action to set the user as the current_user:

def auth
  @user = current_user
end

The auth jbuilder view will look like the following:

json.auth do
  json.auth_token @user[@user.auth_token_field]
  json.secret_key @user[@user.secret_key_field]
end

And when rendered:

{
    "auth": {
      "auth_token": "39e75f277f2edb7d9f547a8b3e21fa4c1c5fd5e213909e7c93a87539e12e60ea",
      "secret_key": "wrlM6KWL0SgyCM0sBbhXfXdwKxqDxC3Df3/wmOwiw2HZOIMvi4L1m51/fhowUz5Ys8BL2vr2GezS4lK4deXGJQ=="
    }
}

The following is an example of how you load the auth details via the angular-hmac-auth module and intercept all $http calls in order to add the HMAC authencation headers:

var hmacAuthApp = angular.module("hmac-auth-demo",['hmacAuthInterceptor'])

    .constant('REST_PATH', "/api/v1")

    .provider('configService', ['REST_PATH',function(REST_PATH) {
        var loadConfig = ['$http','$q', function($http,$q) {
            var deferred = $q.defer();
            $http.get(REST_PATH + '/users/auth').then(function(response) {
                deferred.resolve(response["data"]["auth"]);
            });
            return deferred.promise;
        }];
        this.$get = loadConfig;
    }])

    .config(['$httpProvider', function($httpProvider) {
        $httpProvider.interceptors.push('hmacInterceptor');
    }])

    .run(['hmacInterceptor','REST_PATH', function(hmacInterceptor,REST_PATH){
        hmacInterceptor.whitelist = REST_PATH + '/usrs/auth';
    }])

    .controller('DemoController', [ '$scope','$http','configService','hmacInterceptor','REST_PATH', function($scope,$http, configService, hmacInterceptor, REST_PATH){
        var demo = $scope;

        configService.then(function(config){
            hmacInterceptor.accessId =  config["auth_token"];
            hmacInterceptor.secretKey =  config["secret_key"];
            $http.get(REST_PATH + '/users').then(function(response){
                demo.users = response.data.users;
            });
        });
    }]);

An example of the headers that gets appended to the Angular HTTP requests:

X_HMAC_AUTHORIZATION:APIAuth 39e75f277f2edb7d9f547a8b3e21fa4c1c5fd5e213909e7c93a87539e12e60ea:l+f5GfID1ABZm6W7zAaf+5lb9N4=
X_HMAC_CONTENT_MD5:1B2M2Y8AsgTpgAmY7PhCfg==
X_HMAC_CONTENT_TYPE:
X_HMAC_DATE:Sun, 24 Jan 2016 09:00:06 GMT

The spec/dummy application actually has working version of the above and can be run by setting into the folder and calling rails s

More information

For any more information please contact Bernie Lomax. Bernie!, Bernie!, Bernie!