Add an explicit confirmation: option to has_secure_password

Hi all :waving_hand:

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_challenge precedent 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.