RackApiKey
RackApiKey is a middleware that relies on the client submitting requests with a header named "X-API-KEY" storing their private API key as the value. The middleware will then intercept the request, read the value from the named header and call the given "proc" used for API key lookup. The API key lookup should only return a value if there is an exact match for the value stored in the named API key header. If such an API key exists, the middleware will pass the request onward and also set a new value in the request representing the authenticated API key. Otherwise the middleware will return a HTTP status of 401, and a plain text message notifying the calling requestor that they are not authorized.
Installation
Add this line to your application's Gemfile:
gem 'rack-api-key'
And then execute:
$ bundle
Or install it yourself as:
$ gem install rack-api-key
Usage
use RackApiKey, :api_key_proc => Proc.new { |val| ApiKey.find(val) },
:rack_api_key => "account.api.key",
:header_key => "HTTP_X_CUSTOM_API_HEADER",
:url_restriction => [/api/],
:url_exclusion => [/api\/status/]
:header_key
It's important to note that internally Rack actually mutates any given headers and prefixes them with HTTP and subsequently underscores them. For example if an API client passed "X-API-KEY" in the header, Rack would interpret that header as "HTTP_X_API_KEY". "HTTP_X_API_KEY" is the default header. If you want to use a different header you can specify it in the :header_key options Hash.
:api_key_proc
This is required, there is no default behavior, and the middleware will not work properly unless you specify a Proc that takes one argument. The value the Proc receives will be the value set in the API header key. Use anything you like to determine if the header value is valid. If the value is invalid, the Proc should return nil, otherwise return a value that will ultimately be set in the Rack env.
:rack_api_key
This is the key that will be set in the Rack env with the return value of the :api_key_proc.
:url_restriction
This is an option that can restrict the rack-api-middleware to specific URLs. This works well when you have a mixture of API endpoints that require authentication and some that might not. Or a combination of API endpoints and publicly facing webpages. Perhaps you've scoped all of your API endpoints to "/api", and the rest of the URL mappings or routes are supposed to be wide open.
:url_exclusion
This is an option to allow specific URLs to bypass rack-api-middleware authentication. This works well when you require a single or few endpoints to not require authentication. Perhaps you've scoped all of your API endpoints to "/api" but wish to leave "/api/status" publicly facing.
unauthorized_api_key method
This is a method that can be overridden with however you'd like to respond when a request with an invalid or unauthorized API key is encountered. The default behavior responds with a 401 plain/text message. I find it especially useful to override this method and switch the response to JSON format.
valid_api_key? method
This is another method that can be overridden if there are additional checks and validations beyond the ones already provided. For instance the API key may exist, but for some reason it was temporarily disabled. You could add a check for that here.
rack_api_key_request_setter method
The default behavior of this method will take the return value of the API key proc and set it to the Rack env with the ke specified by :rack_api_key. You may override this method if you prefer setting something else in the Rack env or perhaps nothing at all.
Examples
# Overridden to use the default behavior plus check if the api key is enabled.
def valid_api_key?(api_header_val, api_key_lookup_val)
super && api_key_lookup_val.enabled?
end
# Overridden to respond in JSON format.
def unauthorized_api_key
body_text = {"error" => "blah blah blah"}.to_json
[401, {'Content-Type' => 'application/json; charset=utf-8',
'Content-Length' => body_text.size.to_s},
[body_text]]
end
# Overridden to set the Account attached to the API key instead.
def rack_api_key_request_setter(env, api_key_lookup_val)
env[@options[:rack_api_key]] = api_key_lookup_val.account
end
Rack::Builder.new do
map '/' do
use RackApiKey,
:api_key_proc => Proc.new { |val| ApiKey.find(val) },
:url_restriction => [/api/]
run lambda { |env| [200, {"Content-Type" => "text/html"}, "Testing Middleware"] }
end
map "/all-options" do
use RackApiKey,
:api_key_proc => Proc.new { |val| ApiKey.find(val) },
:rack_api_key => "account.api.key",
:header_key => "HTTP_X_CUSTOM_API_HEADER"
run lambda { |env| [200, {"Content-Type" => "text/html"}, "Testing Middleware"] }
end
end