Ferdinand Rosario Ferdinand Rosario - 4 months ago 15x
Ruby Question

Locking a user using a per-user `maximum_attempts` value with Devise for Rails

As a de-facto standard, we all using Devise for login in our Rails application and will use the Lockable module to lock users after a particular number of failed attempts.

From Devise’s source code and the configuration option

config.maximum_attempts = 20
, I came to understand how Devise performs locking when the user tries to give wrong login credentials. Configuration is statically defined at Rails application boot time in

My expectation is to set the
dynamically – is this possible? If so, please guide me.

I have a superadmin and user below each admin. Based on the super admin I would like to set a different
value for each user during runtime.


One possible way is to monkey-patch the Devise code that you linked to, where attempts_exceeded? is defined. Here’s a guess at what needs to be overridden:

module Devise::Models::Lockable
  # assumes that the User model has a `superadmin` relation
  #  that has a `maximum_attempts` attribute

  def attempts_exceeded?        
    self.failed_attempts >= self.superadmin.maximum_attempts

  def last_attempt?
    self.failed_attempts == self.superadmin.maximum_attempts - 1

This should work, but it would mean that whenever you update Devise, there is a risk of related code breaking, with unknown consequences. So you would have to review the changes to Devise before every update. And if you are discouraged from updating Devise because of this, that may eventually cause security problems if you are too slow to update to a version of Devise with a fixed security problem. So beware of those possible problems.

A safer way that requires more work up-front is to lock the user manually from your own code. The documentation for Devise::Models::Lockable mentions a public method lock_access! that locks the user when you call it. You can set the global config.maximum_attempts to some really high value such as 25. Then, in some callback on the model (I’m not sure which callback), call a method lock_access_based_on_superadmin_limit! that calls lock_access! if necessary according to your custom rules. The following definition is adapted from part of Devise’s valid_for_authentication?:

class User
  # …

  def lock_access_based_on_superadmin_limit!
    if failed_attempts >= superadmin.maximum_attempts
      lock_access! unless access_locked?