I am trying to authenticate my users in a mobile app via email-based Magic Link provided by Ash. The idea is to use Universal Links/AppLinks. The user taps their email like in a web version of the magic link, he receives an email, click on va magic link and with Universal/App links, the application is opened instead of the browser. The mobile application receive the “magic link token”, and then I can ask for an exchange with a JWT token. I also would like to keep the magic link behavior for the web part and creating the user if he does not exist.
What I have done:
- I generated a fresh Ash project with MagicLink authentication (no password strategy)
- Universal/App links work
- I created a new endpoint under the
apipipeline to disable the CSRF for that endpoint and request a magic login link: /mobile/auth/magic-link/request. It works, but I am not sure I do it the Ash way. - Same for login with mobile magic link.
Here is my controller:
def request_mobile_magic_link(conn, %{"email" => email}) when is_binary(email) do
input =
User
|> Ash.ActionInput.for_action(:request_magic_link, %{email: email})
|> Ash.ActionInput.set_context(%{private: %{ash_authentication?: true}})
case Ash.run_action(input) do
:ok ->
send_resp(conn, :ok, "")
{:error, error} ->
Logger.warning("request_magic_link error: #{Exception.message(error)}")
send_resp(conn, :ok, "")
end
end
def sign_in_with_mobile_magic_link(conn, %{"token" => _token} = params) do
User
|> Ash.Changeset.for_create(:sign_in_with_magic_link, params)
|> Ash.Changeset.set_context(%{private: %{ash_authentication?: true}})
|> Ash.create()
|> case do
{:ok, record} ->
{:ok, token, _claims} = AshAuthentication.Jwt.token_for_user(record, %{})
conn
|> put_resp_header("content-type", "application/json")
|> send_resp(:ok, JSON.encode!(%{token: token}))
{:error, error} ->
{:error,
AshAuthentication.Errors.AuthenticationFailed.exception(
strategy: AshAuthentication.Strategy.MagicLink,
caused_by: error
)}
end
end
I add to set the context with %{private: %{ash_authentication?: true}}in both methods otherwise I received a forbidden error due to the forbid_if always()policy, except for the bypass ash_authentication/lib/ash_authentication/checks/ash_authentication_interaction.ex at main · team-alembic/ash_authentication · GitHub
I created another endpoint to login from mobile, as I want to keep the magic login link from web, and I want to return a JWT token on success.
Should I create another bypass, or is it fine to set a private key?
I need two different emails depending if the user is requesting a magic link from the mobile app or from the web app. I would like to pass a mobile?: trueoption to the third argument of the sendmethod but I do not find how to achieve that? I tried to add it to the context.
Here is the action on the User resource (generated by Ash):
create :sign_in_with_magic_link do
description "Sign in or register a user with magic link."
argument :token, :string do
description "The token from the magic link that was sent to the user"
allow_nil? false
end
upsert? true
upsert_identity :unique_email
upsert_fields [:email]
# Uses the information from the token to create or sign in the user
change AshAuthentication.Strategy.MagicLink.SignInChange
metadata :token, :string do
allow_nil? false
end
end
I do not understand the purpose of “metadata :token, :string …” and I didn’t find doc about this. Could someone explain me or point me to a doc/blog post?
It also seems that the AshAuthentication.Strategy.MagicLink.SignInChange, put a JWT token under the metadata of the resource but I am not able to retrieve it with resource.__meta__.token. This is the reason why I generated one in the controller.
Finally, I would like to know if this is the correct Ash way to do things or if I miss something more obvious.
I’m new to Ash, so I admit I’m still struggling with some concepts. Feel free to redirect me to the right documentation resource.






















