Implementing flexible, Stripe-style API authentication in Ruby on Rails
Having dealt with many uncomfortable API authentication schemes in the last decade, I wanted to share the scheme that we’ve found most sustainable for our Ruby on Rails applications. This scheme aligns with that used by services like Stripe. It allows clients to authenticate either via HTTP Basic Auth or an X-AUTH-TOKEN
custom header.
What it will look like
API interactions with the Authentication scheme we are implementing will look as follows:
1 2 |
curl https://yourapp.com/api/resource/123 \ -u sk_test_8d7cd4bc9a8cdfe635d3881d4fc1439d: |
In using the -u
flag, curl is authenticating via HTTP Basic Auth. The client’s API key can also be passed via an X-AUTH-TOKEN
header, which you may find more convenient for implementing clients depending on the features and conventions of the underlying HTTP library you are using.
The basics of Basic Auth
HTTP Basic Auth works via the Authorization
header. This header takes the following form:
1 |
Authorization: <type> <credentials> |
In the case of HTTP Basic Auth, the type
component of the header value will be set to Basic
. The credentials
component is a Base64-encoded string containing the username and the password, separated by a colon character. In our case, we are actually disregarding the password
portion and assuming that the user
portion is the API key. By placing a colon at the end of the credentials we passed with the -u
flag, we are telling curl to pass the key as the user
portion of the Authentication payload and an empty string as the password portion.
When we make the request:
1 2 |
curl https://yourapp.com/api/resource/123 \ -u sk_test_8d7cd4bc9a8cdfe635d3881d4fc1439d: |
The server will see the following:
1 |
Authorization: Basic c2tfdGVzdF84ZDdjZDRiYzlhOGNkZmU2MzVkMzg4MWQ0ZmMxNDM5ZDo= |
If we Base64-decode the credentials part, this expands to:
1 |
Authorization: Basic sk_test_8d7cd4bc9a8cdfe635d3881d4fc1439d: |
Controller-level authentication
With these building blocks, we can implement a controller-level API authentication scheme. We will assume an ApiKey
model with a value
column containing the actual key (sk_test_8d7cd4bc9a8cdfe635d3881d4fc1439d
in our example).
We will support passing of the key via HTTP Basic auth and also an X-AUTH-TOKEN
header. We can therefore implement an ApiController
that performs an authorization based upon these headers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class ApiController < ApplicationController skip_before_action :verify_authenticity_token # Disable CSRF protection before_action :authenticate_client private def authenticate_client key = if request.headers['Authorization'].present? Base64.decode64(request.headers['Authorization'].split(' ').last).remove(':') else request.headers['X-AUTH-TOKEN'] end @api_key = ApiKey.find_by value: key # Now raise an exception if the API key does not exist. # We are assuming the use of the CanCanCan authorization library # here but you can use any method you wish. raise CanCan::AccessDenied if @api_key.nil? end end |
For every action in this controller or any inheriting controller, you can now be assured of the existence of a valid @api_key
in scope. You can use this @api_key
to perform further authorization (such as by tying it to a company or user and implementing a current_ability
method in the case of CanCanCan) and for scoping of resources.
Wrap up
I hope that this article gave some high-level guidance to how to approach Header-based authentication for APIs in Ruby on Rails. This approach is an excellent fit for most authenticated APIs (APIs where you can expect that client credentials will live on a secured host).
For circumstances where credentials cannot be kept private (such as when calls must be made directly from the browser, possibly in a different domain than your application, or when you are providing resource access on behalf of your users to third parties), you may require something like an OAuth scheme instead. In a future post we will be taking a look at this approach as well.
No Comments