Mimimal API Authentication on Rails
We’ve been building a lot of Ember.js authentication mechanisms for several projects, and we’re starting to think that we’ve got it down to a fine art. We’ve been treating my front-end apps as simply an API consumer, which means that they need a similar method of authenticating their API access as that of a tradition (service-based) API client.
A lot of our current thinking on how to put this together can be found in Blogomat, an example application We’re using as a test-bed for ideas of how to build apps with Ember and Rails.
Let’s have a look at our requirements for how we’re going to authorise API client accesses:
- Some sort of short-lived session token which can be acquired by the API client to use when accessing API resources to authorise the client.
- A way of authenticating a user with their username and password to attach it to our session token.
- A way of authenticating a service with a secret that is shared by the server and the client, without that secret being transmitted over the wire.
Some additional design goals include:
- Write the minimum amount needed to implement the required features without adding any overly complicated dependencies to our application (ie Devise).
- Authentication methanisms should be easily understood so that those writing API clients don’t have to struggle to understand what’s going on.
- Try and stay as close to RESTful ideals, and compatible with JSON API as possible.
So, in Blogomat, we have a simple (non-versioned) API namespace in my routes file:
1 2 3 4 5
Seems straight forward enough, let’s create our
first thing we do is create a abstract ApiController class, which (much like the
ApplicationController can encapsulate shared behaviour across
all API endpoints).
1 2 3 4 5 6
API Session Tokens
We know at this point that we’re going to need some sort of api token object
Api::SessionsController to create and return, let’s think about this
object a little. What do we know about it from the requirements?
- We know that it needs some sort of unique identifier.
- We know that it is short-lived, so needs to have a TTL.
- We know that it needs to be able to refer to a user object.
One thing we don’t know is that it needs to be stored in the database, and in fact, since we know that we will be checking this token every time we receive an API request, it’s probably overkill.
We also think that it would be nice if we could store tokens in some sort of fast, shared storage so that if we need to scale our app horizontally we have that capability without massively changing the underlying authorisation scheme. In this vein, we have chosen to store the session tokens in Redis, however you could choose to use Memcache or even just in a Hash, should you want (and in fact, in my tests we have it using a faked-out Redis client backed by a Hash).
The main reason we chose Redis is that it’s reasonably ubiquitous, and has the
handy ability to expire keys, so that we don’t have to worry garbage collecting
ApiSessionTokens. This example simply uses a hash to cut it down to a
reasonable size to demo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
Now that we have an object we can use, let’s alter our sessions controller to return it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
So this is pretty cool. Now if we have receive a POST it’ll return a valid API session token, which will last for 20 minutes and then drop off the map after that.
If they post again with a username and password, or username and api key then we’ll let then sign in as that user for the duration of their session, provided they have provided valid credentials.
Now we can go back and write some helpers in
ApiController to make working
with these sessions easier, and add a before filter to protect API actions from
access without a session token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
The next task is to get password authentication working. We’ve decided to run with the herd and use bcrypt, which saves us having to write our own hashing algorythm. Phew!
1 2 3 4 5 6 7 8 9 10 11 12 13
Now we can create a
UserAuthenticationService to encapsulate our
authentication logic. Basically we just delegate to
1 2 3 4 5 6 7
And wire it up to our sessions controller.
1 2 3 4 5 6 7
Great! Now our user an send an API request to authenticate with their password in order to be able to access the API.
API Key Authentication
The next step is to provide a way of authenticating with a shared-secret, which isn’t transmitted between the client and the API during normal API usage. This sort of authentication is usually used by services trying to consume your API, rather than users themselves.
So, our design goals are as follows:
- Based on a secret shared between the client and server.
- Must be easily computed by both the server and client.
- Must be unique enough that it’s not succeptable to a MITM or Replay attack.
Of course, your API is running over SSL, right?!
With our requirements mapped out, we can implmenent our API key authentication and wire it up to our controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
This has been a much longer blog post than we initially sat down to write, so we’ll stop here. We plan to follow up soon with an entry about implementing the client-side API session token logic in Ember.js.
Thanks for taking the time to read all this!