module ApiAuth

The gem will sign your requests on the client side and authenticate that signature on the server side. If your server resources are implemented as a Rails ActiveResource, it will integrate with that. It will even generate the secret keys necessary for your clients to sign their requests.

Public Class Methods

access_id(request) click to toggle source

Returns the access id from the request’s authorization header

# File lib/api_auth/base.rb, line 41
def access_id(request)
  headers = Headers.new(request)
  if match_data = parse_auth_header(headers.authorization_header)
    return match_data[1]
  end

  nil
end
authentic?(request, secret_key) click to toggle source

Determines if the request is authentic given the request and the client’s secret key. Returns true if the request is authentic and false otherwise.

# File lib/api_auth/base.rb, line 34
def authentic?(request, secret_key)
  return false if secret_key.nil?

  return !md5_mismatch?(request) && signatures_match?(request, secret_key) && !request_too_old?(request)
end
generate_secret_key() click to toggle source

Generates a Base64 encoded, randomized secret key

Store this key along with the access key that will be used for authenticating the client

# File lib/api_auth/base.rb, line 54
def generate_secret_key
  random_bytes = OpenSSL::Random.random_bytes(512)
  b64_encode(Digest::SHA2.new(512).digest(random_bytes))
end
sign!(request, access_id, secret_key) click to toggle source

Signs an HTTP request using the client’s access id and secret key. Returns the HTTP request object with the modified headers.

request: The request can be a Net::HTTP, ActionDispatch::Request, Curb (Curl::Easy) or a RestClient object.

access_id: The public unique identifier for the client

secret_key: assigned secret key that is known to both parties

# File lib/api_auth/base.rb, line 25
def sign!(request, access_id, secret_key)
  headers = Headers.new(request)
  headers.calculate_md5
  headers.set_date
  headers.sign_header auth_header(request, access_id, secret_key)
end

Private Class Methods

auth_header(request, access_id, secret_key) click to toggle source
# File lib/api_auth/base.rb, line 95
def auth_header(request, access_id, secret_key)
  "APIAuth #{access_id}:#{hmac_signature(request, secret_key)}"
end
hmac_signature(request, secret_key) click to toggle source
# File lib/api_auth/base.rb, line 88
def hmac_signature(request, secret_key)
  headers = Headers.new(request)
  canonical_string = headers.canonical_string
  digest = OpenSSL::Digest.new('sha1')
  b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
end
md5_mismatch?(request) click to toggle source
# File lib/api_auth/base.rb, line 74
def md5_mismatch?(request)
  headers = Headers.new(request)
  headers.md5_mismatch?
end
parse_auth_header(auth_header) click to toggle source
# File lib/api_auth/base.rb, line 99
def parse_auth_header(auth_header)
  Regexp.new("APIAuth ([^:]+):(.+)$").match(auth_header)
end
request_too_old?(request) click to toggle source
# File lib/api_auth/base.rb, line 61
def request_too_old?(request)
  headers = Headers.new(request)
  # 900 seconds is 15 minutes
  begin
    if Time.parse(headers.timestamp).utc < (Time.now.utc - 900) then
      raise RequestTooOld, "request is more than 900 seconds old"
    end
    return false
  rescue ArgumentError
    return true
  end
end
signatures_match?(request, secret_key) click to toggle source
# File lib/api_auth/base.rb, line 79
def signatures_match?(request, secret_key)
  headers = Headers.new(request)
  if match_data = parse_auth_header(headers.authorization_header)
    hmac = match_data[2]
    return hmac == hmac_signature(request, secret_key)
  end
  false
end