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
Related
I'm having what may be a simple problem and I cant seem to find a way to fix it though I did find where it is.
What I am trying to do is set up devise password reset with devise_token_auth. I have the email set up with the token generator, and the email links to the end points '/api/auth/passwords/edit' which validates the token and then redirects to my front end password reset form (done in reactjs if it matters), the issue arrises when I actually submit the form, I'm sending the token, I'm also sending the password and confirmation, expiry, uid and all the other headers.
so heres the issue, the devise passwords controller calls the before action set_user_by_token, and i found through some debugging that this is where the issue lies, I created and override module with the default code for the set_users_by_token, and through the use of binding.pry I saw that no values were coming in through the method call, but the method was being called because it hit the pry.
heres the method code
def set_user_by_token(mapping=nil)
# determine target authentication class
rc = resource_class(mapping)
# no default user defined
return unless rc
# gets the headers names, which was set in the initialize file
uid_name = DeviseTokenAuth.headers_names[:'uid']
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
client_name = DeviseTokenAuth.headers_names[:'client']
# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name]
#token ||= request.headers[access_token_name] || params[access_token_name]
#client_id ||= request.headers[client_name] || params[client_name]
# client_id isn't required, set to 'default' if absent
#client_id ||= 'default'
# check for an existing user, authenticated via warden/devise, if enabled
if DeviseTokenAuth.enable_standard_devise_support
binding.pry
devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
if devise_warden_user && devise_warden_user.tokens[#client_id].nil?
#used_auth_by_token = false
#resource = devise_warden_user
# REVIEW: The following line _should_ be safe to remove;
# the generated token does not get used anywhere.
# #resource.create_new_auth_token
end
end
# user has already been found and authenticated
return #resource if #resource && #resource.is_a?(rc)
# ensure we clear the client_id
if !#token
#client_id = nil
return
end
return false unless #token
# mitigate timing attacks by finding by uid instead of auth token
user = uid && rc.find_by(uid: uid)
if user && user.valid_token?(#token, #client_id)
# sign_in with bypass: true will be deprecated in the next version of Devise
if self.respond_to?(:bypass_sign_in) && DeviseTokenAuth.bypass_sign_in
bypass_sign_in(user, scope: :user)
else
sign_in(:user, user, store: false, event: :fetch, bypass: DeviseTokenAuth.bypass_sign_in)
end
return #resource = user
else
# zero all values previously set values
#client_id = nil
return #resource = nil
end
end
here at the very end its hitting the else and returning resource as nil since no other conditions were met.
Any help would be really appreciated, I'm pretty sure this is where the problems is because I've been debugging for days and this is where it lead me
if youre here because you had the same issue i did hopefully i can help! after hours of grueling debugging and testing I overcame, it might be a workaround or crappy way to do it but it works while still using the devise method.
in your passwords_controller.rb there should be a "before_action :set_user_by_token, only: => [:update]"
change ":set_user_by_token" to what ever you want to name this new method we're going to make, then copy and paste the method in the original post and make some minor changes.
change these lines:
uid = request.headers[uid_name] || params[uid_name]
#token ||= request.headers[access_token_name] || params[access_token_name]
#client_id ||= request.headers[client_name] || params[client_name]
to
uid = params[uid_name]
#token ||= params[access_token_name]
#client_id ||= params[client_name]
and done. now you dont have to mess with any initializers or concers!
hopefully I helped somebody!
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
When I login with a username and password by BCrypt checks no problem, everything is fine.
But when I go through the process of recovering password and try to login with the new password the BCrypt never returns true.
The code I have is as follows:
before_save :encrypt_password
before_update :encrypt_password
def authenticate
player = Player.find_by(mail: self.mail)
unless player.nil?
current_password = BCrypt::Password.new(player.password)
if current_password == self.password
player
else
nil
end
end
end
private
def encrypt_password
unless self.password.nil?
self.password = BCrypt::Password.create(self.password)
end
I'm using rails 4
You don't need the before_update callback.
When creating a new record (user in this case), only before_save is triggered. So you get the right behavior.
But when updating a record, both before_update and before_save are triggered, which means your password column is encrypted twice. That's why you get unexpected behavior.
Check this page for more information about callbacks.
What's more, I think it's a bad idea to make password a real column in database. All you need is a column called encrypted_password in database and making password a virtual attribute.
So you can write encrypt_password method like this:
def encrypt_password
unless self.password.nil?
self.encrypt_password = BCrypt::Password.create(self.password)
end
Which gave you no chance to make a mistake like you just made.
I'm migrating from a legacy system that uses simple MD5 unsalted passwords into Devise. While I could roll my own encryptor as recommended on the Devise wiki I actually want to migrate to the bcrypt password mechanism.
This also seems more reasonable than downloading rainbow tables and trying to discover all the plaintext passwords...
So, I'm wondering if there might be any side-effects to the following code, especially around the save! triggering any callbacks that have unintended behavior:
## config/initializers/legacy.rb
require 'bcrypt'
require 'digest/md5'
module Devise
module Models
module DatabaseAuthenticatable
def valid_password?(password)
if self.legacy_password_hash
if ::Digest::MD5.hexdigest(password) == self.legacy_password_hash
## authenticated; now convert to bcrypt password
self.password = password
self.legacy_password_hash = nil
self.save!
return true
else
## so that we don't get a bcrypt invalid hash exception
return false
end
else
return ::BCrypt::Password.new(self.encrypted_password) == "#{password}#{self.class.pepper}"
end
end
end
end
end
Shamelessly stolen from:
http://groups.google.com/group/plataformatec-devise/browse_thread/thread/9dcf87b2225bd11f?pli=1
In short, do not override Devise's default authentication. Just put this method into your authentication model (usually User).
I am trying to convert my custom simple auth system in my rails app to use AuthLogic. I have managed to get everything working fairly easily, but now when I try to login it will not properly validate my credentials. The pertinent code is below:
# app/models/profile.rb
class Profile < ActiveRecord::Base
acts_as_authentic do |c|
c.transition_from_crypto_providers = Authlogic::CryptoProviders::Sha1,
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
end
I used to use this to hash my password on creation:
# app/models/profile.rb
def hash_password
self.salt = ActiveSupport::SecureRandom.base64(8)
self.hashed_password = Digest:SHA1.hexdigest(self.salt + #password)
end
I have already converted all the necessary table columns to be compatible with AuthLogic.
Is there a way to log what AuthLogic is hashing the password as? What else could be wrong?
I solved my problem, I had to write a custom crypto_provider, looked like this for anyone who is curious:
class MyCryptoProvider
# Turns your raw password into a Sha1 hash.
def self.encrypt(*tokens)
tokens = tokens.flatten
tokens = tokens.reverse
digest = Digest::SHA1.hexdigest([*tokens].join)
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def self.matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end