user3813256
user3813256

Reputation: 812

password digest being stored in cleartext using rails4 and use_secure_password

I observed that the password digest is not working correctly in my rails 4 app - the password digest is being stored in cleartext in the database.

user.rb

class User < ActiveRecord::Base
   has_secure_password

   validates :password_digest, length: { minimum: 6 }

end

My user db migrate file is as follows:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :full_name
      t.string :email
      t.string :password_digest

      t.timestamps null: false
    end
  end
end

Any ideas what I am doing wrong?

Edit I have bcrypt enabled in Gemfile (I ran bundle install)

 gem 'bcrypt', '~> 3.1.7'

When I try testing in rails console, I get an invalid hash error:

2.2.1 :011 >   user = User.new(full_name: 'abcd', email: '[email protected]', password_digest: 'abcdef')
 => #<User id: nil, full_name: "abcd", email: "[email protected]", password_digest: "abcdef", created_at: nil, updated_at: nil>
2.2.1 :012 > User.find_by(full_name: 'david').try(:authenticate, 'abcdef')
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."full_name" = ? LIMIT 1  [["full_name", "david"]]
BCrypt::Errors::InvalidHash: invalid hash
        from /usr/local/rvm/gems/ruby-2.2.1/gems/bcrypt-3.1.10/lib/bcrypt/password.rb:60:in `initialize'
        from /usr/local/rvm/gems/ruby-2.2.1/gems/activemodel-4.2.4/lib/active_model/secure_password.rb:102:in `new'
        from /usr/local/rvm/gems/ruby-2.2.1/gems/activemodel-4.2.4/lib/active_model/secure_password.rb:102:in `authenticate'

Upvotes: 1

Views: 3218

Answers (2)

Aleksey Shein
Aleksey Shein

Reputation: 7482

This is strange, everything should work. Try some examples from the docs in your rails console:

user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
user.save                                                       # => false, password required
user.password = 'mUc3m00RsqyRe'
user.save                                                       # => false, confirmation doesn't match
user.password_confirmation = 'mUc3m00RsqyRe'
user.save                                                       # => true
user.authenticate('notright')                                   # => false
user.authenticate('mUc3m00RsqyRe')                              # => user
User.find_by(name: 'david').try(:authenticate, 'notright')      # => false
User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user

If user.save returns false you may look into user.errors for failed validation messages.

EDIT: @madcow finally sorted it right. Right now you need to convert your plain-text passwords, stored in password_digest to bcrypt hashes.

NOTE: You won't be able to restore plain-text passwords back using this code! Make sure to backup your table before-hand if you need plain-text passwords.

Try this code for a single user, and, if everything is ok, apply this technique to other users:

user = User.find_by(name: 'david')
user.password = user.password_confirmation = user.password_digest
user.save!

Here we assign to user.password your plain-text password from user.password_digest. Since has_secure_password redefines password= method, your plain-text password should effectively get stored back to password_digest column, but now as a hash.

To update all users:

User.find_each do |user| 
  user.password = user.password_confirmation = user.password_digest
  user.save!
end

Upvotes: 3

madcow
madcow

Reputation: 2633

You are accidently saving the cleartext password directly in the password digest field, at least in your console example. That is why the password is stored as cleartext and not hashed.

Instead of typing this in your console:

user = User.new(full_name: 'abcd', email: '[email protected]', password_digest: 'abcdef')

Enter this:

user = User.new(full_name: 'abcd', email: '[email protected]', password: 'abcdef', password_confirmation: 'abcdef')
user.save

Then find the user:

User.find_by(full_name: 'abcd').try(:authenticate, 'abcdef')

Never set the :password_digest directly. Always set it through the :password and :password_confirmation fields.

You are getting that invalid hash error, by the way, because it is trying to decrypt your cleartext (not a valid hash) :password_digest value.

Upvotes: 2

Related Questions