How to create email confirmation for user accounts

Hi folks,

Beginner with elixir & AshAuthentication, trying to extend our app with functionality to email a new user with a link for them to confirm their ownership of the email address, and block them from signing in before their email address is confirmed. Seems like we need two routes:

  1. Sends un-confirmed users to a dummy page directing them to confirm their email address via the email (potentially also including an input field for them to enter their email address so they can be re-sent the confirmation email)

  2. Handles inbound confirmation email link clicks.

We have a sender working to send the email to the user upon registration with embedded URI in the form app/auth/user/confirm/<token>.

Also able to manually complete confirmation with the following (per docs) so that the confirmed_at field in the database is populated with a datetime.

strategy = AshAuthentication.Info.strategy!(MyApp.Accounts.User, :confirm)
{:ok, user} = AshAuthentication.Strategy.action(strategy, :confirm, %{"confirm" => “<token>”})

Next step seems to be to create the routes above, unless there are some in-built endpoints that we’re missing?

Anyone able to provide guidance would be greatly appreciated.

Adam

2 Likes

@jimsynz would need to confirm, but I’m pretty sure that there is a confirmation route that ships with ash_authentication that you can use.

As for redirecting to a page to let them know they need to confirm you can do that in the AuthController you likely built as part of following the setup instructions for ash_authentication_phoenix (AFAIK there is no built-in route for this):

  def success(conn, _activity, user, _token) do
    return_to = get_session(conn, :return_to) || ~p"/"
    
  if user.confirmed_at do
    conn
    |> delete_session(:return_to)
    |> store_in_session(user)
    |> assign(:current_user, user)
    |> redirect(to: return_to)
  else 
    conn
    |> put_flash(:error, "You must confirm your email address.")
    |> redirect(to: ...)
  end
end

The predefined route helpers become available for use when you use:

use AshAuthentication.Phoenix.Router

You can see the source of that file here: ash_authentication_phoenix/lib/ash_authentication_phoenix/router.ex at d82b33fe0a9b649e0a65424309f505e82776bd42 · team-alembic/ash_authentication_phoenix · GitHub

There are a lot of macros so it can be a bit daunting for a beginner, they still are for me. I don’t see a confirm route there, but I may be missing something.

UPDATE:

Looks like some routes are created here dynamically based on strategies set on your User resource (or whatever resource you are using for Authentication)

 scope path, scope_opts do
        for strategy <- strategies do
          for {path, phase} <- AshAuthentication.Strategy.routes(strategy) do
            match :*,
                  path,
                  controller,
                  {subject_name, AshAuthentication.Strategy.name(strategy), phase},
                  as: :auth,
                  private: %{strategy: strategy}
          end
        end

So we you need to see what routes your strategy defines if any.

That is defined here: ash_authentication/lib/ash_authentication/add_ons/confirmation/strategy.ex at 71d510efc6acb94771ed2d0f199f6799e2881c77 · team-alembic/ash_authentication · GitHub

 @doc false
  @spec routes(Confirmation.t()) :: [Strategy.route()]
  def routes(strategy) do
    subject_name = Info.authentication_subject_name!(strategy.resource)

    path =
      [subject_name, strategy.name]
      |> Enum.map(&to_string/1)
      |> Path.join()

    [{"/#{path}", :confirm}]
  end

So if I’m following this right, you need to use this router helper:

auth_routes_for Example.Accounts.User, to: AuthController

And have the strategy defined on User and it should dynamically inject that confirm route in there. Try putting that in your router then running: mix phx.routes and see what comes up.

1 Like