Token-Based API Authentication
A friend recently came to me wondering how he could add token-based authentication to his API.
I used Devise for my app, but it looks like they removed token auth. I’ve found a few gems, but they all look to do more than I need.
I’ve been critical of Devise for a long time. I used it exclusively on my earliest Rails sites because of how popular it was and how powerful it was out of the box. But trying to customize Devise is its biggest downside. In this instance, Devise is percevied as your authentication gatekeeper – and homage must be paid.
The good news is: feel free to keep Devise around, but you don’t need it for your API. We can use some relatively unknown methods built right into Ruby and Rails. Here’s how…
We’re going to assign a token to each user. Once the client signs in, they’ll receive a token that can then be used for all authenticated API calls. Let’s get that onto the
This creates the following migration for us:
We’re going to be looking up users based on that
auth_token, so that
add_index method is very important here.
Run the migration:
The Base Controller
I’ve built plenty of Rails-based API’s in my time. The first step for me is usually to make sure I have a good “base” controller for all API controllers. This allows me to set things up apart from
ApplicationController, which typically is heavily bloated with methods that I don’t need in my API. In this case, it will allow me to set custom authentication methods outside of Devise.
There’s a lot to digest here, so let’s break it down:
First, we need a way to protect our controllers:
We’re stubbing out a
authenticate_token method here for now – we want to encapsulate that logic in a separte method. But if it returns a user, we’re all set. Otherwise, we return an error message and a 401 status.
We know that the vast majority of our controllers are not accessible without authentication, so we can run the
require_login! method as a
before_action in this controller. If we need to skip it for certain endpoints (like
sign-in), we can use a
So now we can move on to the
Rails has the
authenticate_with_http_token method built-in. It’ll handle all the details for us here – we just need to know how to lookup the user with the token that’s passed in as a header.
Next, we’ll finish this up with some helper methods like
The finished controller:
So how do we get the token for the user? Just like a normal sessions controller – but we’ll return the token instead of handling setting session info and performing redirects.
First, we’ll add some routes:
And the controller:
We’re using two Devise methods in the
create method here:
valid_password?. If you’re not using Devise, you can easily replace these with your own authentication system. The key here is to load and verify the user. Once this is done, we’ll ask the
User model to give us a token. Note that we’re delegating this responsibilty to the
User model – this is not the job of the controller!
Likewise for destroying this token, we provide a sign out method. We’ve stubbed out
invalidate_auth_token on the
User which we can build next.
Please also note that we don’t immediately error if the user is not found – we’ll load
User.new and still check the password even if it’s blank. This prevents attackers from timing our response times to determine if an email is valid or no.
In the sessions controller, we stubbed out two methods on the
User model – one to generate a token and one to invalidate this token. Let’s fill those in:
SecureRandom to generate a random hexadecimal string. This will generate a 32 character string. For example:
generate_auth_token will generate a new token, update the database, and return the token to be used by our
invalidate_auth_token will simply set this value to
nil and disallow the User to be authenticated with this token in the future.
Now that we’ve got all the moving parts, let’s test things out with
Maybe you don’t like the idea of these auth tokens living on indefinitely. We can easily add some logic to expire these tokens.
First, we need to know when the token was generated at. So we add a
token_created_at field to User:
Note: now we’ll be looking up users now not just by
auth_token, but by
token_created_at. Let’s make sure to add a compound index:
Next, let’s make sure we touch this attribute when we create and destroy tokens:
Now in our base controller, we can check to make sure the token is still “fresh”. Should we validate the user, then check the
token_created_at value? Gadzooks, no! Let’s make our database do that.
The API client will get the
unauthorized response and can attempt to sign in again to fetch a fresh token.
As Ruby developers, we have a littany of incredible Gems available to us. And when faced with the question “Should I build this myself? Or use a gem?”, there’s usually incredible value to not re-inventing the wheel. But make sure your reliance on other libraries never blinds you to simpler solutions that can be fully customized to your needs. Sometimes “rolling your own” can actually save you time.