yuяi
yuяi

Reputation: 2715

Preventing password reuse with Devise

I know that forcing passwords to expire after a certain period from the time the user creates them is not part of Devise logic, and I'm planning to write my own code to make that happen.

It also looks like forcing the user not to reuse one of the last X (in my case 10) passwords will need to be coded manually.

My thinking is that I'll create something like a user_passwords table and use logic in my code to make sure the new password doesn't match any in that table for that user. At the same time I would insert the new password into the table, unless there are 10 records for that user there already, which would mean I'd overwrite the oldest with the new value. Table structure would be something like this:

user_passwords

If anyone has a better, more elegant solution to handle this, I'd appreciate it.

Upvotes: 4

Views: 3504

Answers (4)

Ganesh Sagare
Ganesh Sagare

Reputation: 375

In PasswordsController you can check it.

class PasswordsController < Devise::PasswordsController

  def update
    current_user = User.with_reset_password_token(params[:user][:reset_password_token])
    if current_user && previous_and_new_password_is_same?(current_user)
      current_user.errors[:password] << "has been used previously."
      self.resource = current_user
      respond_with resource
    else
      super
    end
  end

  private

  def previous_and_new_password_is_same?(current_user)
    bcrypt       = ::BCrypt::Password.new(current_user.encrypted_password)
    hashed_value = ::BCrypt::Engine.hash_secret([params[:user][:password], Devise.pepper].join, bcrypt.salt)
    hashed_value == current_user.encrypted_password
  end

end

Here is the documentation of Devise gem for How To Disallow previously used passwords?

Upvotes: 0

Owen Peredo Diaz
Owen Peredo Diaz

Reputation: 154

devise_security_extension doesn't work for me in rails 5, I created my custom:

  • create a migration and add a extra column like: add_column :users, :old_passwords, :text
  • In your model add two callbacks: after_save :cache_old_pass and before_save :verify_old_pass

  • Create the methods of callbacks:

    private
    def verify_old_pass
      if self.encrypted_password_changed?
        old_passwords.to_s.split(',').each do |pass_encrypted|
          if Devise::Encryptor.compare(self.class, pass_encrypted, password)
            errors.add(:base, 'Your password cannot be previous up to 3 back')
            return throw(:abort)
          end
        end
      end
    end
    
    def cache_old_pass # cache last 3 passwords
     if self.encrypted_password_changed?
       update_column(:old_passwords, ([self.encrypted_password] + old_passwords.to_s.split(',')[0, 3]).join(','))
     end
    end
    

Upvotes: -1

jww
jww

Reputation: 102245

I know that forcing passwords to expire after a certain period from the time the user creates them is not part of Devise logic, and I'm planning to write my own code to make that happen.

In practice, the security related studies have found this to be a bad idea. That's because you get diminishing returns on each change. That is, the password starts strong and then gets weaker over time as the user attempts to comply with the policy. See Peter Gutmann's Engineering Security and Chapter 7, Passwords.

From the book, other dumb things include complexity requirements. (Before you object, read the relevant section of the book).


... create something like a user_passwords table and use logic in my code to make sure the new password doesn't match any in that table for that user.

And once you read the chapter, I will be able to ask: why did you allow the user to choose a weak/wounded/broken password in the first place? Those 60 KB Bloom filters are looking mighty useful when combined with Mark Brunett's list of 10 million leaked passwords :)


Preventing password reuse...

The reuse that's going to hurt is password reuse across sites. Brown, Bracken, Zoccoli and Douglas state the numbers are around 70% in Generating and Remembering Passwords (Applied Cognitive Psychology, Volume 18, Issue 6, pp. 641–651). And Das, Bonneau, Caesar, Borisov and Wang report the number around 45% in The Tangled Web of Password Reuse. Note the the Tangled Web study had to crack passwords, so that number is likely higher because they were not able to recover all the passwords

To make reuse a more acute problem, users have to remember passwords for about 25 different sites according to Das, Bonneau, Caesar, Borisov and Wang in The Tangled Web of Password Reuse.

I even got burned with this one a few years ago. I used the same password on two low value accounts. Then GNU's Savannah got hacked and the attackers were able to use the recovered password to breach a little used email account.

Now I just generate a long, random string when I need credentials. I don't even bother writing them down for most sites. When I need to access a site again, I just go through the recovery process.

Upvotes: 9

yuяi
yuяi

Reputation: 2715

The devise_security_extension seems to work for what I need.

However, at present, it doesn't support Devise 2.0 or higher. I ran into a number of issues, and had to downgrade my Devise to 1.5.3. According to comments on their message board, they're currently working on porting the gem to a Devise 2.0 compatible version.

I have given it a spin for its password_expirable and password_archivable modules. Everything seems to work as expected.

It also supports secure_validatable, session_limitable and expirable, the former 2 of which I will probably use in the near future.

Upvotes: 3

Related Questions