Search code examples
ruby-on-railsbcrypt

How do I use bcrypt in rails and validate the password?


Because rails clears the errors during save, I can't do this in my model:

  def password= plain_text
    if plain_text =~ /\d/ && plain_text =~ /\w/
      self.password_digest = BCrypt::Password.create(plain_text)
    else
      self.errors[:password] = "must contain one digit and one word character."
    end
  end

What's the best way to validate a password to have one letter and one digit in rails, while still using bcrypt?


Solution

  • If you are using the Rails has_secure_password option, then Rails will handle the password= method for you. When you set the password Rails will hold onto the unencrypted password in the instance variable @password:

    def password=(unencrypted_password)
      ...
      @password = unencrypted_password
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
      self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
    

    The @password value will remain in memory until the record is saved to the database. So you can run validations against password. When validations pass, the encrypted password_digest value is what will be stored.

    You can see that this is what Rails does when they validate that the password isn't longer than the largest password allowed:

    validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED