ApiSignature
Simple HMAC-SHA1 authentication via headers. Impressed by AWS Requests with Signature Version 4
This gem will generate signature for the client requests and verify that signature on the server side
Installation
Add this line to your application's Gemfile:
gem 'api_signature'
Usage
The usage is pretty simple. To sign a request use ApiSignature::Signer and for validation use ApiSignature::Validator.
Create signature
Sign a request with 'authorization' header. You can change header name, see Configuration section.
api_access_key = 'access_key'
api_secret_key = 'secret_key'
request = {
http_method: 'POST',
url: 'https://example.com/posts',
headers: {
'User-Agent' => 'Test agent'
},
body: 'body'
}
# Sign your request
signature = ApiSignature::Signer.new(api_access_key, api_secret_key).sign_request(request)
# Now apply signed headers to your real request
signature.headers
# signature.headers looks like:
{
"host"=>"example.com",
"x-datetime"=>"2020-01-02T10:24:59.837+0000",
"authorization"=>"API-HMAC-SHA256 Credential=access_key/20200102/web/api_request, SignedHeaders=host;user-agent;x-datetime, Signature=032fc0b7defd66d86ef43ced8e6c3ee351ede21deca6bf1f89b9145f7a9105c1"
}
Validate signature
Validate the request on the client-side. Note, that access_key can be extracted from the request.
# the request to validate
request = {
:http_method=>"POST",
:url=>"https://example.com/posts",
:headers=>{
"User-Agent"=>"Test agent",
"host"=>"example.com",
"x-datetime"=>"2020-01-02T10:24:59.837+0000",
"authorization"=>"API-HMAC-SHA256 Credential=access_key/20200102/web/api_request, SignedHeaders=host;user-agent;x-datetime, Signature=032fc0b7defd66d86ef43ced8e6c3ee351ede21deca6bf1f89b9145f7a9105c1"
},
:body=>"body"
}
# initialize validator with a request to validate
validator = ApiSignature::Validator.new(request)
# get access key from request headers (String)
validator.access_key
# validate the request (Boolean)
validator.valid?('your secret key here')
# get only signed headers (Hash)
validator.signed_headers
Configuration
By default, the generated signature will be valid for 5 minutes This could be changed via initializer:
# config/initializers/api_signature.rb
ApiSignature.setup do |config|
# Time to live, by default 5 minutes
config.signature_ttl = 5 * 60
# Datetime format, by default iso8601
config.datetime_format = '%Y-%m-%dT%H:%M:%S.%L%z'
# Header name, by default authorization
config.signature_header = 'authorization'
# Service name, by default web
config.service = 'web'
end
Testing
In your rails_helper.rb
:
require 'api_signature/spec_support/helper'
RSpec.configure do |config|
config.include ApiSignature::SpecSupport::Helper, type: :controller
end
This will enable the following methods in controller tests:
- get_with_signature(client, action_name, params = {})
- post_with_signature(client, action_name, params = {})
- put_with_signature(client, action_name, params = {})
- patch_with_signature(client, action_name, params = {})
- delete_with_signature(client, action_name, params = {})
client
object should respond to #api_key
and #api_secret
Example usage:
RSpec.describe Api::V1::OrdersController do
let(:client) { FactoryBot.create(:client) }
# or any object, that responds to #api_key and #api_secret
# let(:client) { OpenStruct.new(api_key: 'some_key', api_secret: 'some_api_secret') }
it 'should filter orders by state' do
get_with_signature client, :index, state: :paid
expect(last_response.status).to eq 200
expect(last_response.body).to have_node(:orders)
expect(last_response.body).to have_node(:state).with('paid')
end
let(:order_attributes) { FactoryBot.attributes_for(:order) }
it 'should create new order' do
post_with_signature client, :create, order: order_attributes
end
end
For nested resources path can be specified explicitly using path
parameter:
# path: /api/v1/orders/:order_id/comments
RSpec.describe Api::V1::CommentsController do
let(:client) { OpenStruct.new(api_key: 'some_key', api_secret: 'some_api_secret') }
let(:order) { FactoryBot.create(:order) }
it 'should update comment for order' do
put_with_signature client, :update, path: { order_id: order.id }, comment: { content: 'Some value' }
end
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/api_signature.
License
The gem is available as open source under the terms of the MIT License.