Allowing email change with AshAuthentication

I am trying to figure out how best to implement an email-address change workflow in my Phoenix application using AshAuthentication. The workflow I would like is (written from the user’s point of view),

  1. Log in
  2. Go to user settings
  3. Enter new email address
  4. Re-authenticate
  5. Receive confirmation on new email address
  6. Receive security notice of this action on old email address
  7. Follow URL in confirmation email

Any other emails sent to the user after step 4 but before step 7 still go to the old email address.

It looks like the Confirmation Tutorial implements something more like,

  1. Log in
  2. Perform an update action to change my email address
    (UI unspecified, so this could be equivalent to 2 and 3 above)
  3. Receive confirmation on the new email address
  4. Follow URL in confirmation email

I’m not clear what happens to emails sent between steps 2 and 5.

Is the first workflow even possible with AshAuthentication today?

I believe you are looking for this option: AshAuthentication.AddOn.Confirmation — ash_authentication v4.14.1?

I think that’s only part of the solution. My reading of that is it will prevent the update from being applied until the confirmation URL is visited, so the confirmation email would be sent to the current address, but I would want the confirmation email to go to the new address, otherwise the user could accidentally set the wrong address.

I realised since my last post that for step 4 (Re-authenticate), I would do what the default :change_password action does, and add validate AshAuthentication.Strategy.Password.PasswordValidation to the :change_email action.

Edited to add: I just noticed that AshAuthentication.Sender.send/3 can access the action arguments via the opts argument, so I think I have everything I need.

Right, I was thinking we should be able to send to the new email somehow in this instance so if it’s accessible that sounds like the way, in the sender plus the inhibit updates option.

A docs PR for this would be good as I imagine it’s a common need.

I have managed to get this working, but it has broken the new-user confirmation.

I initially had just this,

confirmation :confirm_email_change do
  monitor_fields [:email]
  require_interaction? true
  confirm_on_create? false
  confirm_on_update? true
  inhibit_updates? true
  sender MyApp.Accounts.User.Senders.SendEmailChangeConfirmationEmail
end

But that would put the user into an “unconfirmed” state, just as if they had not confirmed their current email address, which is bad, so I added confirmed_at_field :email_change_confirmed_at so that it uses a different field for recording the email change confirmation.

With this in place, clicking the “confirm” button in the interaction LiveComponent (from AshAuthenticationPhoenix) hits the MyAppWeb.AuthController.failure/3 function, with this as the reason argument:

%Ash.Error.Invalid{
	bread_crumbs: ["Error returned from: MyApp.Accounts.User.confirm"],
	changeset: "#Changeset<>",
	errors: [
		%Ash.Error.Changes.InvalidArgument{
			field: :confirm,
			message: "is not valid",
			value: nil,
			splode: Ash.Error,
			bread_crumbs: ["Error returned from: MyApp.Accounts.User.confirm"],
			vars: [],
			path: [],
			stacktrace: #Splode.Stacktrace<>,
			class: :invalid
		}
	]
}

And {:confirm_new_user, :confirm} for the activity argument.

This is what my new-user confirmation addon looks like.

confirmation :confirm_new_user do
  monitor_fields [:email]
  require_interaction? true
  confirmed_at_field :confirmed_at
  confirm_on_create? true
  confirm_on_update? false
  auto_confirm_actions [:sign_in_with_magic_link, :reset_password_with_token]
  sender MyApp.Accounts.User.Senders.SendNewUserConfirmationEmail
end

Are tokens getting mixed up somehow?

You can have multiple confirmation strategies, one for updating email and one for the initial registration backed by different fields.

That’s what I’m trying to do, but they seem to be conflicting somehow.

      add_ons do
        log_out_everywhere do
          apply_on_password_change? true
        end

        confirmation :confirm_new_user do
          monitor_fields [:email]
          require_interaction? true
          confirmed_at_field :confirmed_at
          confirm_on_create? true
          confirm_on_update? false
          auto_confirm_actions [:sign_in_with_magic_link, :reset_password_with_token]
          sender MyApp.Accounts.User.Senders.SendNewUserConfirmationEmail
        end

        confirmation :confirm_email_change do
          monitor_fields [:email]
          require_interaction? true
          confirmed_at_field :email_change_confirmed_at
          confirm_on_create? false
          confirm_on_update? true
          inhibit_updates? true
          sender MyApp.Accounts.User.Senders.SendEmailChangeConfirmationEmail
        end
      end
    end

Hmm…not sure. That ought to work I think. May need a reproduction.

For the benefit of anyone else reading this thread, I have raised Multiple confirmation addons seem to clash · Issue #1053 · team-alembic/ash_authentication · GitHub