Matt Hughes tinkerer, grower, craftsman

Securing a Rails API with Devise

I'm currently working on a whisky tasting and bottle inventory application, and have gotten to the point where I need to add some user security. I decided to try Devise, even though it's more focused on web application security than API security.

Devise previously supported authentication tokens, but removed TokenAuthenticatable from their codebase in late 2013. Fortunately, devise_token_auth was developed, which adds token authentication to Devise by installing an additional gem.

Generate your code

After installing the devise_token_auth gem, run this command to generate your /auth routes and User model:

rails generate devise_token_auth:install User auth

Run db:migrate to write your changes to the database. I added the following code to my seeds.db to go on and add a user to the users table:

User.create({
  uid: 'GenBurnside',
  username: 'GenBurnside',
  provider: 'email',
  name: 'Matt Hughes',
  email: 'matthughes.tech@gmail.com',
  password: 'asdfasdfasdf',
  password_confirmation: 'asdfasdfasdf'
})

Get an access token

With a user created, now we can try validating the credentials, and making an authorized API call.

POST /auth/sign_in
{
    "email": "matthughes.tech@gmail.com",
    "password": "asdfasdfasdf"
}

If you send the correct credentials, the response will contain the User object, and several headers we need to make authorized requests.

Access-Token:   NyUIH_nI3vsKkNFtu3hmOA
Client:         R9H84prlUIrrYPXr4iKuDQ
Expiry:         1438378648
Token-Type:     Bearer
UID:            matthughes.tech@gmail.com

You can verify the access token by sending the headers above to the GET auth/validate_token route.

Make an authorized request

You need to add before_action :authenticate_user! to the controller you want to protect:

class ThingController < ApplicationController
    before_action :authenticate_user!

    def action
        user_id = current_user.id
        render :json => { message: "It worked!" }
    end
end

This will require authentication for all actions in that controller. Other requests will return a 401 Unauthorized.

Do another POST /auth/sign_in to get the authentication headers you need, and send them to GET /thing. You should get a 200 OK response.

If you wait more than five seconds and use the same token again, you'll get a 401 Unauthorized response. This is because devise_token_auth issues a new token after every request by default. I disabled this feature while developing the API for simplicity. To disable, edit config/initializers/devise_token_auth.rb, and add the following line:

config.change_headers_on_each_request = false

Accessing the user in the controller

current_user will get the current Devise user for that request. current_user.id is used in the example above. If you wanted to access their username or e-mail, you could use current_user.username or current_user.email.