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 users
table.
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:
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 skip_before_action
there.
So now we can move on to the authenticate_token
method:
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 current_user
and user_signed_in?
.
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: find_for_database_authentication
and 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:
We’re using 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 SessionsController
. 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 curl
.
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 auth_token
and 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.
Happy coding!
Writer. Musician. Adventurer. Nerd.
Purveyor of GIFs and dad jokes.