8

I'm porting a project from Rails 3 to 3.1. My authentication system was easily switched over to the new has_secure_password in ActiveRecord. The only problem I'm running into is that I also use OmniAuth and I have the system set up so if a user signs up using one of the OmniAuth providers the account shouldn't require a password. I can't seem to override the password_digest validations setup by has_secure_password. Is there anyway to turn off those validations and write my own, or am I going to have to just use my old hand written bcrypt functions from my Rails 3 version of the site?

Scott Martin
  • 620
  • 6
  • 14

2 Answers2

5

I ended up going back to using custom methods. However, I did realize afterwards that I should've been able to use a before_validation callback to check the conditions, then if they matched set the password_digest to anything simple like '0'. This way the digest would never be blank, but at the same time it shouldn't ever validate as a correct password, making them sign in through OmniAuth.

Feel free to correct me if I'm mistaken.

Scott Martin
  • 620
  • 6
  • 14
  • I am going to try your method, let me know if you find a better way. – Chris Slade Sep 28 '11 at 23:57
  • 3
    This seems like the easier fix. I just set `user.password_digest = SecureRandom.urlsafe_base64` in my `from_omniauth` method. (Although, after rereading your answer, I guess it doesn't have to be randomized because it's not being used for authentication?). Given this is so much quicker--I wonder what the rationale is for opting for the accepted answer? – umezo Oct 24 '12 at 06:07
  • You're right. I went ahead and switched the answer. Although I've still opted for using BCrypt directly and writing my own password hashing methods. It's too easy to do and makes your intentions clearer. – Scott Martin Nov 02 '12 at 06:30
  • Setting `user.password_digest = SecureRandom.urlsafe_base64` or something else definitely seems safer than using something like `0` since someone could still try to log in to the account directly. I added a `dummy_password` boolean column to my user model, and set it to `false` for omniauth registrations. If such a user then wants to set a password later, I set it to `true`. Until then, "current password" fields are hidden if `dummy_password` is `true`. – LouieGeetoo Mar 15 '13 at 02:01
  • Unless I'm mistaken, a bcrypt hash would never have fewer than 60 characters. Let alone equal 0. Which should make it impossible for anyone to attempt to log in using a password. – Scott Martin Mar 16 '13 at 03:19
  • This way I need to add :password_digest to attr_accessible ? Is it safe? – Rafael Oliveira Jun 25 '13 at 03:41
  • 2
    @RafaelOliveira unless your setting :password_digest through a form, which you shouldn't be, you don't need to add it to attr_accessible. – Scott Martin Jun 27 '13 at 20:59
4

Scott, your idea is correct. I've been wrestling with this problem to no avail. I tried to override 'has_secure_password' and it simply won't work. No matter where I stuck the code.

Instead I have the following:

class User < ActiveRecord::Base
  has_secure_password

  validates_presence_of :password, :on => :create, :if => :password_required

  # Associations
  has_many :authentications

  # Callbacks
  before_validation :no_password_omniauth

  # Gets set to true if the caller is trying to authenticate with omniauth.
  @called_omniauth = false

  # Build new omniauth users
  def apply_omniauth(omniauth)
    authentications.build(
    :provider => omniauth['provider'], 
    :uid => omniauth['uid'])
    self.first_name = omniauth['user_info']['first_name'] if self.first_name.blank?
    self.last_name = omniauth['user_info']['last_name'] if self.last_name.blank?
    self.email = omniauth['user_info']['email'] if omniauth['user_info']['email'] && self.email.blank?
    @called_omniauth = true
  end

  def password_required
    return false if @called_omniauth == true
    (authentications.empty? || !password.blank?)
  end

  private

  def no_password_omniauth
    self.password_digest = 0 unless password_required
  end

end

The apply_omniauth method gets called from the controller when someone is trying to authenticate or sign up.

Thanks for the idea you nailed it.

crankin
  • 41
  • 3
  • I switched the answer to yours since you provided a code example. Thanks. I assume then, that it wouldn't let you log into that account using any kind of password? – Scott Martin Sep 30 '11 at 02:58
  • Also, what are you using `@called_omniauth` for exactly? Wouldn't just using `authentications.empty? || !password.blank?` in your `password_required` method be enough? – Scott Martin Sep 30 '11 at 03:14
  • called_omniauth is the only way your application knows the user is signing up through omniauth (by the apply_omniauth method). – chourobin Oct 31 '11 at 11:08