Token Based Authentication in Rails
By Carlos Souza
Token based authentication is when an API client uses a token identifier to make authenticated HTTP requests.
A lot of popular services offer token based authentication for connecting with their web API, like HipChat, Campfire, Backpack, Last.fm and many others. It’s not yet a standard, but there is an official draft that specifies the scheme.
Token based authentication offers many benefits over HTTP Basic and Digest Authentication:
- More convenience, as we can easily expire or regenerate tokens without affecting the user’s account password.
- Better security if compromised, since vulnerability is limited to API access and not the user’s master account.
- The ability to have multiple tokens for each user, which they can use to grant access to different API clients.
- Greater control for each token, so different access rules can be implemented.
Getting an API token usually means visiting a profile settings page on the service’s website and requesting an access key. Some might already have a key generated for us.
The Authorization header format for Token based authentication looks like so:
Rails offers the authenticate_or_request_with_http_token method, which automatically checks the Authorization request header for a token and passes it as an argument to the given block:
Inside that block is where we implement our authentication strategy. In the following example, we’ll authenticate our requests for the EpisodesController class.
Using a before_action, we call the authenticate_or_request_with_http_token method. We only care about the first argument, which is the token we’ll use to look up the user.
It is very important that the auth_token is unique across all users. In our User model, we use a before_create callback to generate the token.
The token generation process is delegated to SecureRandom.uuid. This method is part of Ruby’s standard library, and returns a Universally Unique Identifier (RFC 4122) that’s guaranteed to be unique across a global namespace (the gsub call removes dashes so the token is URL friendly). And if we wanted to take it a step further, we could also add a database level constraint, but we’ll skip that for now.
Using curl, we can test our token based authentication by passing a valid token in the Authorization header:
If the authentication fails and our block returns false, the request is halted and our application immediately responds with a 401 - Unauthorized status code.
According to the HTTP spec, a 401 - Unauthorized response must include a WWW-Authenticate header with a challenge applicable to the requested resource. The authenticate_or_request_with_http_token automatically includes that header for us:
The Token part means that the given resource uses token authentication. The resource under that URI is currently part of the “Application” realm. The realm value allows protected resources to be partitioned into different sets of protection spaces, each with its own access policies.
The default realm value used by Rails is “Application”. To change it to a more descriptive value, we can pass the new name as an argument to the authenticate_or_request_with_http_token method.
One limitation we might come across when using authenticate_or_request_with_http_token is the fact that this method doesn’t allow for much customization. For example, it always responds with the Content-Type set to HTML regardless of the mime type requested by the API client. There’s also no easy way to add a custom error message to the response body if we wanted to.
For more flexibility, we can use the authenticate_with_http_token method and manually build the response ourselves:
And that gives us a proper JSON response:
For more information about token based authentication, the draft is available at http://tools.ietf.org/html/draft-hammer-http-token-auth-01. The code examples for this blog post are available on the BananaPodcast project on GitHub.
We cover this and other API topics in our Surviving APIs with Rails course on Code School. Even if you are not a Code School subscriber, you can still checkout the first level for free!
Have you ever implemented a token based authentication on your Rails API ? We’d love to hear your opinion in our comments!
– Carlos Souza (@caike)
(photo source: http://www.flickr.com/photos/kolix/2539213620)
UPDATE – 2014-11-01: Updated the post to use SecureRandom.uuid