Hello,
I have spent more than a day reading ash code, and this forum to understand how tokens work, but I still have some questions.
I figured out I can get the token like this:
user =
Ash.Query.for_read(User, :sign_in_with_password, %{
email: "email@example.com",
password: "test"
})
|> Ash.read_one!()
token = Ash.Resource.get_metadata(user, :token)
But that token is not stored anywhere. I have read this in the generated resource code:
# In the generated sign in components, we validate the
# email and password directly in the LiveView
# and generate a short-lived token that can be used to sign in over
# a standard controller action, exchanging it for a standard token.
# This action performs that exchange. If you do not use the generated
# liveviews, you may remove this action, and set
# `sign_in_tokens_enabled? false` in the password strategy.
What does it mean? An example of this in the docs would be nice
I signed in using LiveView /sign-in
but still I don’t see any token generated.
I feel this is a stupid question, but what is the workflow for getting the token and using it in the API?
Based on this reply, I understand I can create an API endpoint to sign in and get the password:
use Phoenix.Controller
def sign_in(conn, %{"username" => username, "password" => password}) do
YourUserResource
|> Ash.Query.for_read(:sign_in_with_password, %{username: username, password: password})
|> YourApi.read_one()
|> case do
{:ok, user} ->
conn |> put_status(200) |> json(%{token: user.__metadata__.token})
{:error, error} ->
# handle errors. You should get back an `Ash.Error.Forbidden`
# error with a nested error you can use to provide an error message
end
end
But after trying that token in the :sign_in_with_token
action, it doesn’t work.
read :sign_in_with_token do
# In the generated sign in components, we validate the
# email and password directly in the LiveView
# and generate a short-lived token that can be used to sign in over
# a standard controller action, exchanging it for a standard token.
# This action performs that exchange. If you do not use the generated
# liveviews, you may remove this action, and set
# `sign_in_tokens_enabled? false` in the password strategy.
description "Attempt to sign in using a short-lived sign in token."
get? true
argument :token, :string do
description "The short-lived sign in token."
allow_nil? false
sensitive? true
end
# validates the provided sign in token and generates a token
prepare AshAuthentication.Strategy.Password.SignInWithTokenPreparation
metadata :token, :string do
description "A JWT that can be used to authenticate the user."
allow_nil? false
end
end
I get the error:
password: Email or password was incorrect
I attach a screenshot of the error in ash admin.
I also tried in iex
:
iex(17)> user = Ash.Query.for_read(User, :sign_in_with_token, %{token: token}) |> Ash.read_one!()
* (Ash.Error.Forbidden)
Bread Crumbs:
> Error returned from: Iapruebo.Accounts.User.sign_in_with_token
Forbidden Error
* Authentication failed
(ash_authentication 4.3.5) lib/ash_authentication/errors/authentication_failed.ex:5: AshAuthentication.Errors.AuthenticationFailed."exception (overridable 2)"/1
(ash_authentication 4.3.5) lib/ash_authentication/errors/authentication_failed.ex:22: AshAuthentication.Errors.AuthenticationFailed.exception/1
(ash_authentication 4.3.5) lib/ash_authentication/strategies/password/sign_in_with_token_preparation.ex:52: anonymous fn/3 in AshAuthentication.Strategy.Password.SignInWithTokenPreparation.verify_token_and_constrain_query/2
(ash 3.4.46) lib/ash/actions/read/read.ex:2364: anonymous fn/2 in Ash.Actions.Read.run_before_action/1
(elixir 1.17.3) lib/enum.ex:4858: Enumerable.List.reduce/3
(elixir 1.17.3) lib/enum.ex:2585: Enum.reduce_while/3
(ash 3.4.46) lib/ash/actions/read/read.ex:2363: Ash.Actions.Read.run_before_action/1
(ash 3.4.46) lib/ash/actions/read/read.ex:582: anonymous fn/8 in Ash.Actions.Read.do_read/5
(ash 3.4.46) lib/ash/actions/read/read.ex:938: Ash.Actions.Read.maybe_in_transaction/3
(ash 3.4.46) lib/ash/actions/read/read.ex:319: Ash.Actions.Read.do_run/3
(ash 3.4.46) lib/ash/actions/read/read.ex:82: anonymous fn/3 in Ash.Actions.Read.run/3
(ash 3.4.46) lib/ash/actions/read/read.ex:81: Ash.Actions.Read.run/3
(ash 3.4.46) lib/ash.ex:2105: Ash.do_read_one/3
(ash 3.4.46) lib/ash.ex:2053: Ash.read_one/2
(ash 3.4.46) lib/ash.ex:2018: Ash.read_one!/2
(elixir 1.17.3) src/elixir.erl:386: :elixir.eval_external_handler/3
(stdlib 6.2) erl_eval.erl:919: :erl_eval.do_apply/7
(stdlib 6.2) erl_eval.erl:663: :erl_eval.expr/6
(elixir 1.17.3) src/elixir.erl:364: :elixir.eval_forms/4
(ash 3.4.46) lib/ash/error/forbidden.ex:3: Ash.Error.Forbidden.exception/1
(ash 3.4.46) /home/antares/proyectos/iapruebo/deps/splode/lib/splode.ex:264: Ash.Error.to_class/2
(ash 3.4.46) lib/ash/error/error.ex:108: Ash.Error.to_error_class/2
(ash 3.4.46) lib/ash/actions/read/read.ex:390: anonymous fn/3 in Ash.Actions.Read.do_run/3
(ash 3.4.46) lib/ash/actions/read/read.ex:335: Ash.Actions.Read.do_run/3
(ash 3.4.46) lib/ash/actions/read/read.ex:82: anonymous fn/3 in Ash.Actions.Read.run/3
(ash 3.4.46) lib/ash/actions/read/read.ex:81: Ash.Actions.Read.run/3
(ash 3.4.46) lib/ash.ex:2105: Ash.do_read_one/3
(ash 3.4.46) lib/ash.ex:2053: Ash.read_one/2
(ash 3.4.46) lib/ash.ex:2018: Ash.read_one!/2
iex:17: (file)
I am sure I’m misunderstanding some principle, but I can’t figure out what.
A workflow example in the docs would be nice. Once I understand it, I can add it to the docs.
Thanks a lot!