I'm trying to implement what seems like a very simple authentication approach using Sinatra and BCrypt but clearly I'm missing something...
Users are preassigned a temporary password which is stored in plaintext in the db.
I authenticate against the temp password and then create both a salt and password_hash and write them as strings to the db (mongo in this case).
To authenticate I fetch the salt from the db and user password to compare.
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret(params[:password], password_salt)
user.set(:password_hash => password_hash)
user.set(:password_salt => password_salt)
end
end
post "/auth" do
@user = User.first(:email => params[:email])
@user_hash = BCrypt::Password.new(@user.password_hash) #because the password_hash is stored in the db as a string, I cast it as a BCrypt::Password for comparison
if @user_hash == BCrypt::Engine.hash_secret(params[:password], @user.password_salt.to_s) then
auth = true
else
auth = false
end
end
The value returned by BCrypt::Engine.hash_secret(params[:password], password_salt) is different than what is stored in the db (both are of class BCrypt::Password, but they don't match).
What am I missing here? Many thanks in advance for any insight!
Marc
BCrypt::Password
is a subclass of String
, and it overrides the ==
method to make checking passwords easier. When you do
if @user_hash == BCrypt::Engine.hash_secret(params[:password], @user.password_salt.to_s)
you end up performing the hash twice, and so they don’t match. If you compared directly with @user.password_hash
rather than using BCrypt::Password.new
you should see that they match.
The more “correct” way to use bcrypt-ruby for passwords is to not use the Engine
class at all, just the Password
class. You don’t need to manage the salt yourself, bcrypt takes care of that and includes it in the password hash string:
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret("s3kr1t!", password_salt)
puts password_salt
puts password_hash
produces something like this:
$2a$10$4H0VpZjyQO9SoAGdfEB5j.
$2a$10$4H0VpZjyQO9SoAGdfEB5j.oanIOc4zp3jsdTra02SkdmhAVpGK8Z6
You’ll get something slightly different if you run it, since a different salt will be generated, but you can see that the password hash includes the salt.
In your case, you want something like this:
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_hash = BCrypt::Password.create(params[:password])
user.set(:password_hash => password_hash) # no need to store the salt separately in the database
end
end
post "/auth" do
@user = User.first(:email => params[:email])
@user_hash = BCrypt::Password.new(@user.password_hash)
if @user_hash == params[:password] then # overridden == method performs hashing for us
auth = true
else
auth = false
end
end