APIs with Devise
Preface
Authentication with Rails can be a touchy subject. But I’m not going to get into the debate of whether or not you should roll your own, use something like Devise, throw Devise away and just use Warden, or follow any other of available methods for building authentication into your application.
This last week I fielded more questions in IRC and at a local beginner rails group about handling authentication via an API than I did about setting up an actual authentication framework, so that’s what I’m going to write about.
Handling API Authentication with Devise
You may have already seen Ryan Bates’ Railscast on Securing an API, which discusses token authentication over an HTTP header, but are wondering how to integrate that into your existing authentication paradigm. Since I end up using Devise for authentication on a majority of my smaller projects, that’s what I’ll assume you’re using for the purposes of this tutorial.
Even though there are other guides to Token Authentication with Devise out there, they’re a bit hidden in documentation, and most aren’t exactly up-to-date.
The crux of the idea is this:
- We want to create a minimal authentication system whereby we receive a username, email, or other user identifier, and a password; after authenticating the user, we’ll return an authentication token for use in subsequent requests [1].
- We should, in all subsequent requests, allow a user to be authenticated via their authentication token.
- We should be reasonably careful in handling this authentication information.
To satisfy our criteria, I tend to use something similar as to what’s suggested on this gist by @josevalim on github combined with a few other ideas [2], with a couple of tweaks:
|
|
|
Notice how we use Devise.secure_compare to compare the token in the database with the token given in the params, mitigating timing attacks. |
|
|
And of course, the accompanying methods for our User.rb:
|
|
|
Sarbanes-Oxley Compliance: http://en.wikipedia.org/wiki/Sarbanes%E2%80%93Oxley_Act |
|
|
What we’re doing in the ApplicationController is protecting all requests to our application by raising an exception if the authenticity token is invalid; however, we’re making sure that the session for all JSON requests is treated as an empty session. You can read up on all of that at the ActionController::RquestForgeryProtection doc site.
In this fashion, we simply have the CSRF session return null, and we neglect to store it while authenticating via authentication token. This mitigates any session based attacks on your application’s JSON endpoints.
A couple of tweaks that I’ve made compared to the gist linked above are related to the way authentication information is sent over to the application:
|
|
Rather than sending the data over parameters, we’re expecting the client application to send it via two headers: “X-API-EMAIL” and “X-API-TOKEN”; this cleans up the endpoint URIs.
SessionsController.rb
What’s a SessionsController? Well, if you’re using Devise for authentication, you might not realise that you have one. It handles (simply put) the creating and destroying of logged in user sessions for your application.
We’ll need to override this slightly to handle user authentication via the API. The Ember Rails4 Starter Kit had a good starting place, and I believe that’s where I got the initial template for this:
|
|
Without delving into a conversation about the inner workings of Devise and Warden, what we’re doing here is overriding Devise’s SessionsController with our own. Ours sends html requests back up to Devise’s SessionsController, but handles JSON requests a bit differently:
- On #destroy, we authenticate via the ‘X-API-TOKEN’ header sent by the client, and reset the users’ authentication token.
- On #create, we return the user’s email and authentication token. These
are all that are required for subsequent API requests - until we either reset
the token (typically via TTL) or the user resets the token via #destroy.
Enabling CanCan Authorization
Oh, and as an added bonus for all of you CanCan users out there still trying to wrangle it to work with Rails 4/Strong Parameters and Devise, here’s a magical snippet to toss into your ApplicationController.rb:
|
|
|
Fix for CanCan/Strong Parameters (expecting format ex: /api/v1/resource) |
|
|
|
Enable CanCan authorization by default |
Rather than delving into CanCan’s inner workings here, I’ll just direct you to these two issues:
- How does CanCan handle mass-assignment? (Issue #571)
- Make check_authorization conditional with block or options (Issue #284)
[1]: Setting and enforcing a TTL is an exercise left to the reader.
[2]: There are a number of gists that re-use some of the same code snippets I’m pasting below, but tracing back the original source can be tough.