Reading the Source · Card 10
What does has_secure_password install?
One line. A whole authentication setup: virtual password attribute, hashing on assignment, an authenticate method, and validations. Plus one column requirement Rails won't budge on.
The familiar line
The Rails-canonical way to handle passwords. The model has a password_digest column.
class User < ApplicationRecord
has_secure_password
end The question
Name everything has_secure_password adds to a User instance and to the User class. Plus the one column requirement.
Take a moment. Think about: where the plain password lives, where the hashed password lives, how you set it, how you check it, and what validations come along for the ride.
What gets installed
On every User instance:
# Virtual attributes (not columns, just attr_accessors):
user.password
user.password = "secret" # also computes password_digest via BCrypt
user.password_confirmation
user.password_confirmation = "secret"
# The authentication method:
user.authenticate("secret") # returns the user if BCrypt matches, false otherwise
# (For Rails 7.1+: an authenticate_by class method on User.)
User.authenticate_by(email: "...", password: "secret") On the User class, validations get installed automatically:
- Presence of
passwordon create only (so existing users can save without re-entering it). - Confirmation:
password_confirmationmust matchpasswordif supplied. - Length: max 72 characters (BCrypt's limit).
Required: a password_digest column on the users table. Rails stores the BCrypt hash there. The plain password attribute is never persisted; it exists in memory only while the request is alive.
Also required: the bcrypt gem in your Gemfile. Rails won't pull it in for you.
Where this happens in Rails
Defined in activemodel/lib/active_model/secure_password.rb. The method overrides password= to call BCrypt::Password.create(value) and store the result in password_digest. The authenticate method on the instance reads the digest, lets BCrypt compare, and returns either the user or false.
Why this is worth knowing
"Why is password nil after I reload the user?" comes up regularly. It's because password is a virtual attribute, alive only on the in-memory instance. The persistent thing is password_digest. Knowing that this is a Rails-Way mixin with a known shape means you can read auth code in a codebase you haven't seen before, and you know what user.authenticate(pw) is doing without grepping for the method.