Hi all ![]()
I ran into a common friction point with has_secure_password and wanted to
propose a small, backward-compatible addition.
The problem
By default, has_secure_password only validates that password_confirmation
matches password when it happens to be present
(validates_confirmation_of :password, allow_nil: true). It never requires
password_confirmation to be present at all, so a signup form can silently
save a user who never filled in the confirmation field.
The natural first attempt to fix this is:
validates :password_confirmation, presence: true
but this runs on every save, not just create, so a later update that
doesn’t touch the password at all (e.g. changing address) fails because
password_confirmation is blank.
This exact friction is why rails/rails#25373 was filed. The suggested
workaround there was scoped to :password with on: :create, but for
:password_confirmation specifically, the fix that’s taught in courses like
Stanza’s Rails Foundations is:
validates :password_confirmation, presence: true,
if: -> { new_record? || password.present? }
This works, but every app that wants a “confirm your password” field ends up reimplementing the exact same line.
Proposal
Add an opt-in confirmation: keyword to has_secure_password, in the same
spirit as password_challenge (#43688) — opt-in, value-driven, no change to
the default behavior. has_secure_password has already grown similar opt-in
keywords since then (reset_token:, algorithm:), so this would follow an
established pattern:
has_secure_password confirmation: true
which would wire up the presence + create/change validation above
automatically. The current default (confirmation: false) keeps today’s
behavior unchanged.
Example
class User < ApplicationRecord
has_secure_password confirmation: true
end
user = User.create!(password: "abcd1234", password_confirmation: "abcd1234")
same_user = User.find(user.id)
same_user.password_confirmation # => nil, it's a virtual attribute, not a column
same_user.update(address: "somewhere new") # => true, no password_confirmation needed
Why I think this fits has_secure_password
- Fully backward compatible (off by default).
- Mirrors the existing
password_challengeprecedent for narrowly-scoped, opt-in validations on the same method. - Removes boilerplate that a lot of apps (and tutorials) reimplement identically.
Curious what the core team thinks — happy to put together a PR (tests + docs) if this seems like a good direction.