Joan Gavelán
Joan Gavelán
AEAsh Elixir
Created by Joan Gavelán on 5/2/2025 in #support
Custom Authentication Flow with AshAuthentication in Phoenix + React (Inertia.js)
Cool. I'd like your input on the final piece of my authentication flow - confirming new users. I'm requiring users to confirm their accounts in order to log in. This is my implementation so far:
def generate_user_confirmation_token(user) do
now = DateTime.utc_now()
changeset = Ash.Changeset.for_update(user, :update, %{"confirmed_at" => now})

Contactly.Accounts.User
|> AshAuthentication.Info.strategy!(:confirm_new_user)
|> AshAuthentication.AddOn.Confirmation.confirmation_token(changeset, user)
end

def confirm_user(token) do
Contactly.Accounts.User
|> AshAuthentication.Info.strategy!(:confirm_new_user)
|> AshAuthentication.AddOn.Confirmation.Actions.confirm(%{"confirm" => token})
end
def generate_user_confirmation_token(user) do
now = DateTime.utc_now()
changeset = Ash.Changeset.for_update(user, :update, %{"confirmed_at" => now})

Contactly.Accounts.User
|> AshAuthentication.Info.strategy!(:confirm_new_user)
|> AshAuthentication.AddOn.Confirmation.confirmation_token(changeset, user)
end

def confirm_user(token) do
Contactly.Accounts.User
|> AshAuthentication.Info.strategy!(:confirm_new_user)
|> AshAuthentication.AddOn.Confirmation.Actions.confirm(%{"confirm" => token})
end
And the controller using these functions:
def create(conn, %{"email" => email}) do
with {:ok, user} <- Accounts.get_user_by_email(email),
{:ok, token} <- Accounts.generate_user_confirmation_token(user) do
SendNewUserConfirmationEmail.send(user, token, [])
end

conn
|> put_flash(
:info,
"If your email exists in our system, you will receive a confirmation email."
)
|> redirect(to: ~p"/login")
end

def update(conn, %{"token" => token}) do
case Accounts.confirm_user(token) do
{:ok, _user} ->
conn
|> put_flash(:success, "Your account has been confirmed successfully!")
|> redirect(to: "/login")

{:error, _error} ->
conn
|> put_flash(:error, "The token is invalid or expired.")
|> redirect(to: "/confirm-user")
end
end
def create(conn, %{"email" => email}) do
with {:ok, user} <- Accounts.get_user_by_email(email),
{:ok, token} <- Accounts.generate_user_confirmation_token(user) do
SendNewUserConfirmationEmail.send(user, token, [])
end

conn
|> put_flash(
:info,
"If your email exists in our system, you will receive a confirmation email."
)
|> redirect(to: ~p"/login")
end

def update(conn, %{"token" => token}) do
case Accounts.confirm_user(token) do
{:ok, _user} ->
conn
|> put_flash(:success, "Your account has been confirmed successfully!")
|> redirect(to: "/login")

{:error, _error} ->
conn
|> put_flash(:error, "The token is invalid or expired.")
|> redirect(to: "/confirm-user")
end
end
What do you think? It works but I think there might be a more "Ash" way to do it
9 replies
AEAsh Elixir
Created by Joan Gavelán on 5/2/2025 in #support
Custom Authentication Flow with AshAuthentication in Phoenix + React (Inertia.js)
Yeah... I was missing that, silly mistake on my part. So after some research I ended up with the following implementation:
def update(conn, params) do
with {:ok, claims, _resource} <- AshAuthentication.Jwt.verify(params["reset_token"], User),
{:ok, user} <- Accounts.get_by_subject(%{subject: claims["sub"]}, authorize?: false),
{:ok, _user} <- Accounts.reset_password_with_token(user, params, authorize?: false) do
conn
|> put_flash(:success, "Password reset successful.")
|> redirect(to: ~p"/login")
else
{:error, %Ash.Error.Invalid{} = error} ->
conn
|> assign_errors(error)
|> redirect(to: ~p"/password-reset/#{params["reset_token"]}")

_ ->
conn
|> put_flash(:error, "The token is invalid or expired. Please request a new one.")
|> redirect(to: ~p"/password-reset")
end
end
def update(conn, params) do
with {:ok, claims, _resource} <- AshAuthentication.Jwt.verify(params["reset_token"], User),
{:ok, user} <- Accounts.get_by_subject(%{subject: claims["sub"]}, authorize?: false),
{:ok, _user} <- Accounts.reset_password_with_token(user, params, authorize?: false) do
conn
|> put_flash(:success, "Password reset successful.")
|> redirect(to: ~p"/login")
else
{:error, %Ash.Error.Invalid{} = error} ->
conn
|> assign_errors(error)
|> redirect(to: ~p"/password-reset/#{params["reset_token"]}")

_ ->
conn
|> put_flash(:error, "The token is invalid or expired. Please request a new one.")
|> redirect(to: ~p"/password-reset")
end
end
What do you think about it?
9 replies