"BCrypt::Errors::InvalidHash: invalid hash" when trying to authenticate password - ruby-on-rails

I have a model User:
class User < ActiveRecord::Base
has_secure_password
end
I'm using gem 'bcrypt', '3.1.11'. But for some reason the authenticate method doesn't work.
I have a User record user where user.password_digest == "password". But typing in user.authenticate('password') in console returns the following error:
> user.authenticate('password')
BCrypt::Errors::InvalidHash: invalid hash
from /home/.rvm/gems/bcrypt-3.1.11/lib/bcrypt/password.rb:60:in 'initialize'
I don't even think the argument is supposed to be a hash: isn't it just supposed to be a string? What's going on here?

It is not a Hash object, it refers to the hash value in password_digest which seems to be invalid, that is, you set it incorrectly.
Try this:
user.password = 'password'
user.password_confirmation = 'password'
user.save
Now you can authenticate with:
user.authenticate('password')

Related

How to update password attribute with has_secure_password?

I used has_secure_password for the User model. Now I am trying to use AJAX to update some of the user's attributes, including password. However, it looks like with has_secure_password, the password attribute no longer exists, replaced by password_digest. So when I am trying to do
user[:password] = "The password passed by AJAX"
user.save!
I got:
ActiveModel::MissingAttributeError (can't write unknown attribute
password)
The question is: What is the right way to update a user's password in this situation? Do I need to manually compute the hash and update the password_digest?
EDIT:
I am using Rails 4.2.1
Normally you just use:
user = User.find 1
user.password = 'Test123456789'
user.save
But, it sounds like you have not added a password_digest column to the users table or have not run the migrations.

Devise and User.create

Usually when we create a model, say User, its attributes match with database fields.
For example, if my corresponding database table users_development has fields name and score then when I create an instance of the User class I simply type user = User.create(:name => "MyName", :score => 85).
Now, Devise created a migration file including fields email and encrypted_password, but I cannot see the field password (which is quite logically from the security point of view).
While looking through forum posts I saw many examples like User.create(:email =>"me#domain.com", :password => "foo"). So, where did the password come from? It is not a field of table users_development. What is going on behind the scene? I looked through the documentation on http://rubydoc.info/github/plataformatec/devise/master/Devise but couldn't find any explanation.
User.create(:email => "me#domain.com", :password => "foo") does not directly create a database record with those exact fields. Rather, it uses public_send("#{k}=", v) for each pair in the parameters hash. So really, it's doing something like this internally:
user = User.new
user.email = "me#domain.com"
user.password = "foo"
user.save
Even though you don't have a password database field, Devise's DatabaseAuthenticatable module adds a password= method, which updates the encrypted_password field:
def password=(new_password)
#password = new_password
self.encrypted_password = password_digest(#password) if #password.present?
end
This method from devise source code does the trick :
# Generates password encryption based on the given value.
def password=(new_password)
#password = new_password
self.encrypted_password = password_digest(#password) if #password.present?
end
When calling create, update attributes, build, etc, rails will try to call for each field the method field=, so when you pass :password => 'foo' to create it will do something like :
user = User.new
user.password = 'foo'
user.save
Here this method allows to build the model with an unhashed password but to store the hashed password in the database.

How to match a new string with the password already encrypted

I am working on a rails 3.2.13 project. I am using devise plugin (devise gem 3.2.2, 1.4.2) for authentication. Using this plugin, how can I validate the current_password field while changing the old password to a new one? Or else, please suggest how I can achieve this by encrypting the given string and matching it with the password already saved without using the devise plugin.
E.g.: One user has encrypted_password like below:
"$2a$10$VrawKYj6zp10XUxbixVzE.7d4QgYjQn9aiuzAuP7fp3PZOLMP5wbu"
while changing the password, if I enter a current_password, it should match the string above (encrypted_password == current_password). How can I validate this?
I believe you need to break your problem down into the following steps:
Determine if the old_password is actually the user's current password.
To do this, you can call:
User.find_by_id([SOME_ID]).valid_password?(old_password)
If this returns true, then you can move on to the next step to begin changing of the password. If it doesn't, then the old_password is incorrect, and you should not allow the changing of password.
The implementation of valid_password? can be found in the Devise gem's /lib/devise/models/database_authenticatable.rb file (at around Line 40). You could use this implementation to roll your own code for validating a password. But, Devise pretty much does it for you if you call valid_password?, so rolling your own seems unnecessary.
If old_password is valid, then verify that new_password matches confirm_new_password.
if (new_password == confirm_new_password)
.
.
.
end
If these match, then set the new password by doing the following:
u = User.find_by_id([SOME ID])
u.password = new_password
u.password_confirmation = confirm_new_password
u.save
You can verify that the password has been changed by:
u.valid_password?(new_password)
Update user with current_password validation:
#user.update_with_password(account_update_params)
# account_update_params - should have :current_password, :password, :password_confirmation
It is default behaviour in Devise::RegistrationsController. If you want update user without password, you should overwrite controller's action
class UsersController < Devise::RegistrationsController
def update_resource(resource, params)
# resource.update_with_password(params)
resource.update_attributes(params)
end
end
Do I understand you right what you want allow users login with encrypted and unencrypted (usual) password?
We have:
user.valid_password?('Password2').should
code on github
So we can overwrite it inside models/user.rb
def valid_password?(password)
encrypted_password == password || super(password)
end

Converting existing password hash to Devise

I'm trying to convert an existing Admin model to Devise. We already have a password hash but it's obviously not Devise compatible. What I would like to do is accept the login form and check the provided password against the encrypted password. If it's not correct, use the old hash to check the password and if it matches, empty the old password_hash field and set Devise's password to the provided password and save the model.
What's the best way to move forward? I suspect that I need to override something, perhaps in a custom controller, but I'm not entirely sure how to proceed.
You can let Devise do the "hard work" of encrypting the password with the new crypt scheme, as shown in https://gist.github.com/1704632:
class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
super(password)
rescue BCrypt::Errors::InvalidHash
return false unless Digest::SHA1.hexdigest(password) == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
true
end
end
end
Using the bcrypt encryptor in Devise, this is what I ended up doing with my legacy data:
In models/user.rb
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
Digest::SHA1.hexdigest(password) == encrypted_password
end
end
As you can see, devise throws an InvalidHash exception when it encounters an invalid hash, which it would do when authenticating a legacy user.
I use this to fall back to the hashing-algorithm used to create the original legacy hash.
It doesn't change the password though, but that could simply be added to the method if needed.
First you need to copy password_salt and encrypted_password to your new object model
Using this because I have to export my database User to another application and old,
app are using devise 1.0.x and new app using 2.1.x
Class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
salt = password_salt
digest = nil
10.times { digest = ::Digest::SHA1.hexdigest('--' << [salt, digest, password, nil].flatten.join('--') << '--') }
digest
return false unless digest == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
self.password_salt = nil # With this you will knew what object already using the new authentication by devise
self.save
true
end
end
end
If you're moving from SHA512, the solution is a bit more involved than moeffju's SHA1 solution:
def valid_password?(password)
if has_legacy_password?
return false unless valid_legacy_password?(password)
convert_legacy_password!(password)
true
else
super(password)
end
end
protected
def has_legacy_password?
password_salt.present?
end
def convert_legacy_password!(password)
self.password = password
self.password_salt = nil
self.save
end
def valid_legacy_password?(password)
stretches = 10
salt = password_salt
pepper = nil
digest = pepper
stretches.times do
tokens = [salt, digest, password, pepper]
digest = Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
end
Devise.secure_compare(encrypted_password, digest)
end
Be sure to replace stretches and pepper with the values you used to encrypt the passwords.
follow Thomas Dippel instructions i have made a gist that update password:
https://gist.github.com/1578362
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
#SOURCES OF SOLUTION:
# http://stackoverflow.com/questions/6113375/converting-existing-password-hash-to-devise
# https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/crypto_providers/sha512.rb
# https://github.com/plataformatec/devise/blob/master/lib/devise/encryptors/authlogic_sha512.rb
alias :devise_valid_password? :valid_password?
def valid_password?(password)
debugger
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
stretches = 20
digest = [password, self.password_salt].flatten.join('')
stretches.times {digest = Digest::SHA512.hexdigest(digest)}
if digest == self.encrypted_password
#Here update old Authlogic SHA512 Password with new Devise ByCrypt password
# SOURCE: https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
# Digests the password using bcrypt.
# Default strategy for Devise is BCrypt
# def password_digest(password)
# ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
# end
self.encrypted_password = self.password_digest(password)
self.save
return true
else
# If not BCryt password and not old Authlogic SHA512 password Dosn't my user
return false
end
end
end

Authlogic: can't set password attribute from within class

I have a User model that acts_as_authentic for AuthLogic's password management. AuthLogic adds "password" and "password_confirmation" attributes over top of the db-backed "crypted_password" attribute. This is pretty standard AuthLogic stuff.
I want to have a method that sets both password and password_confirmation at the same time (useful for internal applications where I'm not worried about typos). To do this I created a new method in User:
#user.rb
def password_and_confirm=(value)
password = value
password_confirmation = value
end
However calling this method does not seem to actually set the password:
user = User.new
user.password = "test"
user.password # => "test"
user.crypted_password # => a big base64 string, as expected
user = User.new
user.password_and_confirm = "test"
user.password # => nil
user.crypted_password # => nil
I also tried a different route:
def internal_password(value)
password = value
end
...and got the same problem.
Why can't I set the password attribute from within a method inside the User class?
Better try this:
#user.rb
def password_and_confirm=(value)
self.password = value
self.password_confirmation = value
end
Otherwise ruby tries to treat the methods (as it is implemented as such) as local variables (this has precedence during assignment operations).

Resources