Skipping devise trackable module for API calls

Prathamesh Sonpatki

Prathamesh Sonpatki

October 30, 2018

We use devise gem for authentication in one of our applications. This application provides an API which uses token authentication provided by the devise gem.

We were authenticating the user using auth token for every API call.

class Api::V1::BaseController < ApplicationController
  before_action :authenticate_user_using_x_auth_token
  before_action :authenticate_user!

  def authenticate_user_using_x_auth_token
    user_email = params[:email].presence || request.headers['X-Auth-Email']
    auth_token = request.headers['X-Auth-Token'].presence

    @user = user_email && User.find_by(email: user_email)

    if @user && Devise.secure_compare(@user.authentication_token, auth_token)
      sign_in @user, store: false
    else
      render_errors('Could not authenticate with the provided credentials', 401)
    end
  end
end

Everything was working smoothly initially, but we started noticing significant reduction in the response times during peak hours after a few months.

Because of the nature of the business, the application gets API calls for every user after every minute. Sometimes the application also get concurrent API calls for the same user. We noticed that in such cases, the users table was getting locked during the authentication process. This was resulting into cascading holdups and timeouts as it was affecting other API calls which were also accessing the users table.

After looking at the monitoring information, we found that the problem was happening due to the trackable module of devise gem. The trackable module keeps track of the user by storing the sign in time, sign in count and IP address information. Following queries were running for every API call and were resulting into exclusive locks on the users table.

UPDATE users SET last_sign_in_at = '2018-01-09 04:55:04',
current_sign_in_at = '2018-01-09 04:55:05',
sign_in_count = 323,
updated_at = '2018-01-09 04:55:05'
WHERE users.id = $1

To fix this issue, we decided to skip the user tracking for the API calls. We don't need to track the user as every call is stateless and every request authenticates the user.

Devise provides a hook to achieve this for certain requests through the environment of the request. As we were already using a separate base controller for API requests, it was easy to skip it for all API calls at once.

class Api::V1::BaseController < ApplicationController
  before_action :skip_trackable
  before_action :authenticate_user_using_x_auth_token
  before_action :authenticate_user!

  def skip_trackable
    request.env['warden'].request.env['devise.skip_trackable'] = '1'
  end
end

This fixed the issue of exclusive locks on the users table caused by the trackable module.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.