I just started a new project with Rails 4.1 and explored the has_secure_password feature. Really awesome stuff!
I hope you are not storing passwords in clear text to your database! You should always store some kind of hashed values instead of clear text passwords. In case somebody steals your database he/she still doesn’t has the passwords.
There are a couple good tutorials how to hash and store passwords in a secure way. I implemented it a couples times by myself with Ruby.
A more sophisticated solution is devise, a very robust gem for security and authentication.
However. Since Rails 3.1 you can take advantage of has_secure_password. This mechanism in Rails takes care of password validation and encryption. It requires a field ‘password_digest’ in your model, where it will store the encrypted password. Let’s generate a simple model.
rails g model user username:string password_digest:string
Let’s add this line to the user model.
has_secure_password
This will add an attribute password
and password_confirmation
to your model. This 2 fields are now part of your model but not part of the database schema! Because we don’t want to store cleartext passwords.
Let’s add some tests.
require 'spec_helper' describe User do it "fails because no password" do User.new({:username => "hans"}).save.should be_false end it "fails because passwrod to short" do User.new({:username => "hans", :password => 'han'}).save.should be_false end it "succeeds because password is long enough" do User.new({:username => "hans", :password => 'hansohanso'}).save.should be_true end end
3 very simple tests. Persisting a new user without password should fail. Persisting a new user with too short password should fail as well. And creating a new user with a long password should succeed. If you run this tests with RSpec the 2nd test will fail. By default Rails doesn’t has a validation for the length of the password. So let’s add it to our user model.
class User < ActiveRecord::Base has_secure_password validates :password, :length => { :minimum => 5 } end
If you run the tests again, they will be green. If you take a look into your database you will see that the user table/collection has a column password_digest
with a very cryptical value. But there are no columns for password! That’s exactly what we wanted.
Now lets do the authentication. Assume a new user signed up at your portal and now he wants to login. This is how you authenticate him.
user = User.find_by_username("USERNAME").authenticate("CLEAR_TEXT_PASSWORD")
If the username and the clear text password from the HTTP Request is correct it will return a valid user from the database. Otherwise it will return nil!
has_secure_password
validates the password on creation time, the very first time you persist it. It doesn’t check the password field after that, for updates for example. And that’s OK. Because that means you can load a user later from db, change it and persist it without knowing the password.
Another feature of this mechanism is password confirmation. has_secure_password
also adds an attribute password_confirmation
to your model. This attribute gets only validated if it’s not nil. If it’s not nil it must be equal to the password attribute. Let’s add 2 more tests for that one.
it "fails because password confirmation doesnt match" do User.new({:username => "hans", :password => 'hansohanso', :password_confirmation => 'aa'}).save.should be_false end it "succeeds because password & confirmation match" do User.new({:username => "hans", :password => 'hansohanso', :password_confirmation => 'hansohanso'}).save.should be_true end
To make this tests pass you have to add one more line to the model.
class User < ActiveRecord::Base has_secure_password validates :password, :length => { :minimum => 5 } validates_confirmation_of :password end
The line “validates_confirmation_of :password” will check the password confirmation.
Rails doesn’t force you to have a password confirmation for your model, but if you want it you can turn it on.
I really like this feature because it saves me a lot of code and development time. And for most applications this is really enough.
Let me know what you think about this, either in the comments or on Twitter.
I was wondering about the actual implementation of `has_secure_password`, took a look at the source code and recognized it didn’t store any salt. I wondered, because I thought using salts is a best practice. It turned out that the bcrypt algorithm used to hash the password works differently than I thought it would work. Here is a great explanation: http://stackoverflow.com/a/6833165/104959
I was also wondering about the salt value. Thanks for the link. Now it makes more sense.
How can i use another name of “password_digest”, i want to use api_password. Have u ever try that? Thank you.
I don’t know. I used the default values!
When I updated the existing user without touching the password field, I get error message:
@messages={:password=>[“is too short (minimum is 5 characters)”]}
The issue you are having, is that you have given the password certain validations and when you update the model, the password is not meeting these validations because you are leaving it blank.
You just need to alter your validations slightly to allow an UPDATE of the model to have different password validations.
Try this type of code:
“`
#if this attr on the user model is nil (i.e. new user) – run these validations
validates :password,
presence: true,
length: { minimum: 6 },
if: “password_digest.nil?”
#if updating user (not a new user)
validates :password,
length: { minimum: 6 }, #if they enter someting at least 6 great!
allow_blank: true #but at this stage, blank would also be allowed, because that would retain current password
“`