Created
November 20, 2012 02:06
-
-
Save bowsersenior/4115493 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'openssl' | |
require 'base64' | |
require 'uri' | |
# inspired by Amazon's AWS auth (also used by Twilio) | |
# see: | |
# * http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/ | |
# * https://github.com/amazonwebservices/aws-sdk-for-ruby/blob/master/lib/aws/core/signer.rb | |
# * https://github.com/amazonwebservices/aws-sdk-for-ruby/blob/master/lib/aws/core/signature/version_4.rb | |
# * https://github.com/twilio/twilio-ruby/blob/master/lib/twilio-ruby/util/request_validator.rb | |
# | |
# Usage: | |
# sda = SimpleDigestAuth.new :api_key => '123' | |
# | |
# sda.sign( | |
# :request_method => 'GET', | |
# :body => "", | |
# :path => '/', | |
# :query_string => 'foo=bar' | |
# ) | |
# # => "q5o3wVHT1MFlzjViCKi5ZBEbx/aVu0OgFrL707FUAZQ=" | |
# | |
# sda.valid_signature?( | |
# :request_method => 'GET', | |
# :body => "", | |
# :path => '/', | |
# :query_string => 'foo=bar', | |
# :signature => "q5o3wVHT1MFlzjViCKi5ZBEbx/aVu0OgFrL707FUAZQ=" | |
# ) | |
# # => true | |
class SimpleDigestAuth | |
module ArgumentsHelper | |
def require!(opts, *required_options) | |
raise "Must pass a hash of options" unless opts.is_a? Hash | |
missing_options = required_options - opts.keys | |
raise "Missing required option(s):#{missing_options}" unless missing_options.empty? | |
end | |
end | |
include ArgumentsHelper | |
attr_accessor :api_key | |
def initialize(opts={}) | |
require! opts, :api_key | |
self.api_key = opts[:api_key] | |
end | |
def sign(opts={}) | |
require! opts, :request_method, :body, :path, :query_string | |
cr = self.class.canonical_request_for(opts) | |
self.class.build_signature_for( | |
:canonical_request => cr, | |
:api_key => self.api_key | |
) | |
end | |
def valid_signature?(opts={}) | |
require! opts, :request_method, :body, :path, :query_string, :signature | |
received_signature = opts.delete(:signature) | |
calculated_signature = self.sign(opts) | |
received_signature == calculated_signature | |
end | |
private | |
class << self | |
include ArgumentsHelper | |
def canonical_request_for(opts={}) | |
require! opts, :request_method, :body, :path, :query_string | |
[ | |
opts[:request_method], | |
opts[:body], # POST params | |
opts[:path], | |
opts[:query_string] # GET params | |
].join("\n") | |
end | |
def build_signature_for(opts={}) | |
digest = OpenSSL::Digest::Digest.new('sha256') | |
Base64.encode64( | |
OpenSSL::HMAC.digest(digest, opts[:api_key], opts[:canonical_request]) | |
).strip | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a plain rack application... | |
# ...it is very simple! | |
# For a nice intro, see: | |
# * http://rubylearning.com/blog/a-quick-introduction-to-rack/ | |
require 'openssl' | |
require 'base64' | |
# inspired by Amazon's AWS auth (also used by Twilio) | |
# see: | |
# * http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/ | |
# * https://github.com/amazonwebservices/aws-sdk-for-ruby/blob/master/lib/aws/core/signer.rb | |
# * https://github.com/amazonwebservices/aws-sdk-for-ruby/blob/master/lib/aws/core/signature/version_4.rb | |
# * https://github.com/twilio/twilio-ruby/blob/master/lib/twilio-ruby/util/request_validator.rb | |
module SimpleRackAuth | |
class << self | |
attr_accessor :api_key | |
end | |
def canonical_request_for(request_method, path, query_string, body) | |
[ | |
request_method, | |
path, | |
query_string, # GET params | |
body # POST params | |
].join("\n") | |
end | |
def build_signature_for(canonical_request, api_key) | |
digest = OpenSSL::Digest::Digest.new('sha256') | |
Base64.encode64( | |
OpenSSL::HMAC.digest(digest, api_key, canonical_request) | |
).strip | |
end | |
def valid_signature?(received_signature, canonical_request) | |
calculated_signature = build_signature_for( | |
canonical_request, | |
SimpleRackAuth.api_key | |
) | |
received_signature == calculated_signature | |
end | |
module_function :build_signature_for, :canonical_request_for, :valid_signature? | |
# Rack middleware to implement the auth solution | |
# Usage: | |
# | |
# use SimpleRackAuth::Middleware | |
class Middleware | |
include SimpleRackAuth | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
canonical_request = canonical_request_for( | |
env['REQUEST_METHOD'], | |
env['REQUEST_PATH'], | |
env['QUERY_STRING'], | |
env['rack.input'].read | |
) | |
received_signature = env['HTTP_X_SIMPLE_RACK_AUTH_SIGNATURE'] | |
if valid_signature?(received_signature, canonical_request) | |
status, headers, body = @app.call(env) | |
headers['X-SimpleRackAuth-Authorized-Canonical-Request'] = canonical_request | |
headers['X-SimpleRackAuth-Authorized-Signature'] = received_signature | |
[status, headers, body] | |
else | |
[401, {'Content-type' => 'text/plain'}, ['Unauthorized']] | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment