AF
Ash Framework•5mo ago
EAJ

Manually sign in a user in Phoenix

I have a homegrown invitation solution where admin users can invite other users. I did an experiment with using the magic link strategy, but my thinking is that I don't want to actually create the user record until the invitation is accepted. The following Phoenix controller handles accepting an invitation:
defmodule DrengWeb.InvitationController do
use DrengWeb, :controller

def show(conn, %{"token" => token}) do
case Dreng.Accounts.get_invitation_by_token(token) do
{:ok, invitation} ->
render(conn, :valid_invitation, token: token, role: invitation.role)

{:error, _} ->
conn
|> put_status(404)
|> render(:invalid_invitation)
end
end

def create_user(conn, %{"token" => token}) do
case Dreng.Accounts.accept_invitation(token) do
{:ok, user} ->
conn
# log in here?
|> assign(:current_user, user)
|> redirect(to: ~p"/signup")

{:error, _} ->
conn
|> put_flash(:error, "This invitation is not valid")
|> redirect(to: ~p"/")
end
end
end
defmodule DrengWeb.InvitationController do
use DrengWeb, :controller

def show(conn, %{"token" => token}) do
case Dreng.Accounts.get_invitation_by_token(token) do
{:ok, invitation} ->
render(conn, :valid_invitation, token: token, role: invitation.role)

{:error, _} ->
conn
|> put_status(404)
|> render(:invalid_invitation)
end
end

def create_user(conn, %{"token" => token}) do
case Dreng.Accounts.accept_invitation(token) do
{:ok, user} ->
conn
# log in here?
|> assign(:current_user, user)
|> redirect(to: ~p"/signup")

{:error, _} ->
conn
|> put_flash(:error, "This invitation is not valid")
|> redirect(to: ~p"/")
end
end
end
The Dreng.Accounts.accept_invitation is a create action on the User resource which validates the invitation token and creates a user. I am probably missing something simple, but I'm not sure how to go about persisting the user to the session in a way that is compatiable with AshAuth? Is it possible without creating a new strategy?
Solution:
This might a bit dirty, but by adding the GenerateTokenChange and borrowing the magic link signin strategy I got my action to work ```diff create :sign_up_with_invitation do argument :token, :string, allow_nil?: false ...
Jump to solution
10 Replies
barnabasj
barnabasj•5mo ago
I didn't verify it, but I assume that if you do the same thing that is shown in the success callback from the auth controller, that, that should work. https://github.com/team-alembic/ash_authentication_phoenix/blob/903f3a386e1aba2f7b070187ef6f31215a92bdfd/lib/ash_authentication_phoenix/controller.ex#L20-L20
EAJ
EAJOP•5mo ago
Right, sorry, should have mentioned that I tried that. It then seems to hiccup on the require_token_presence_for_authentication? option:
[error] ** (KeyError) key :token not found in: %{selected: [:id, :email, :role, :inserted_at, :updated_at]}
(ash_authentication 4.9.6) lib/ash_authentication/plug/helpers.ex:18: AshAuthentication.Plug.Helpers.store_in_session/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:20: DrengWeb.InvitationController.create_user/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:1: DrengWeb.InvitationController.action/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:1: DrengWeb.InvitationController.phoenix_controller_pipeline/2
(phoenix 1.8.0-rc.4) lib/phoenix/router.ex:416: Phoenix.Router.__call__/5
(dreng 0.1.0) lib/dreng_web/endpoint.ex:1: DrengWeb.Endpoint.plug_builder_call/2
(dreng 0.1.0) deps/plug/lib/plug/debugger.ex:155: DrengWeb.Endpoint."call (overridable 3)"/2
(dreng 0.1.0) lib/dreng_web/endpoint.ex:1: DrengWeb.Endpoint.call/2
(phoenix 1.8.0-rc.4) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.7.0) lib/bandit/pipeline.ex:131: Bandit.Pipeline.call_plug!/2
(bandit 1.7.0) lib/bandit/pipeline.ex:42: Bandit.Pipeline.run/5
(bandit 1.7.0) lib/bandit/http1/handler.ex:13: Bandit.HTTP1.Handler.handle_data/3
(bandit 1.7.0) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
(bandit 1.7.0) lib/bandit/delegating_handler.ex:8: Bandit.DelegatingHandler.handle_info/2
(stdlib 6.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
[error] ** (KeyError) key :token not found in: %{selected: [:id, :email, :role, :inserted_at, :updated_at]}
(ash_authentication 4.9.6) lib/ash_authentication/plug/helpers.ex:18: AshAuthentication.Plug.Helpers.store_in_session/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:20: DrengWeb.InvitationController.create_user/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:1: DrengWeb.InvitationController.action/2
(dreng 0.1.0) lib/dreng_web/controllers/invitation_controller.ex:1: DrengWeb.InvitationController.phoenix_controller_pipeline/2
(phoenix 1.8.0-rc.4) lib/phoenix/router.ex:416: Phoenix.Router.__call__/5
(dreng 0.1.0) lib/dreng_web/endpoint.ex:1: DrengWeb.Endpoint.plug_builder_call/2
(dreng 0.1.0) deps/plug/lib/plug/debugger.ex:155: DrengWeb.Endpoint."call (overridable 3)"/2
(dreng 0.1.0) lib/dreng_web/endpoint.ex:1: DrengWeb.Endpoint.call/2
(phoenix 1.8.0-rc.4) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.7.0) lib/bandit/pipeline.ex:131: Bandit.Pipeline.call_plug!/2
(bandit 1.7.0) lib/bandit/pipeline.ex:42: Bandit.Pipeline.run/5
(bandit 1.7.0) lib/bandit/http1/handler.ex:13: Bandit.HTTP1.Handler.handle_data/3
(bandit 1.7.0) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
(bandit 1.7.0) lib/bandit/delegating_handler.ex:8: Bandit.DelegatingHandler.handle_info/2
(stdlib 6.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
I've tried going over the docs but don't quite understand everything that's going on here. I'll keep digging
ZachDaniel
ZachDaniel•5mo ago
We really just need a utility for this 😅 @jart I think we may want a testing adaptation of the code that sets the user into the session and provie it. Because now downstream things expect the token to be present its even more difficult to hand-roll it. WDYT?
EAJ
EAJOP•5mo ago
So currently this isn't something that is well supported, and I might want to re-visit the magic link strategy for doing invitations as well?
ZachDaniel
ZachDaniel•5mo ago
Oh sorry I misunderstood The error that you are seeing is common in a testing scenario Correct magic links weren't made to back invites
EAJ
EAJOP•5mo ago
No worries, I'll try to explain better: I'm doing invites only. When an invite is created I generate a token and send it in a link to the invited user. When they accept the invite I create a user using the role and email associated with the invite record. At the same time, I want to log in the newly created user from the controller. In short: I just created a user, how can I log them in in my own controller? Or do I need to make a call to the auth controller somehow?
ZachDaniel
ZachDaniel•5mo ago
You can mimic the logic from your auth controller
EAJ
EAJOP•5mo ago
I'm trying to do that a little bit, but I'm having an issue with AshAuthentication.Plug.Helpers.store_in_session/3 which expects a token to be present. Thinking more about this I should probably use the Token resource to generate the tokens instead of :crypto.strong_rand_bytes which is what I'm doing now?
Solution
EAJ
EAJ•5mo ago
This might a bit dirty, but by adding the GenerateTokenChange and borrowing the magic link signin strategy I got my action to work
create :sign_up_with_invitation do
argument :token, :string, allow_nil?: false

+ change {AshAuthentication.GenerateTokenChange, strategy_name: :magic_link_signin}
change Dreng.Changes.ValidateInvitation
end
create :sign_up_with_invitation do
argument :token, :string, allow_nil?: false

+ change {AshAuthentication.GenerateTokenChange, strategy_name: :magic_link_signin}
change Dreng.Changes.ValidateInvitation
end
ZachDaniel
ZachDaniel•5mo ago
Yeah, I think thats actually fine 🙂

Did you find this page helpful?