I stumbled across a strange behavior which I could not reproduce debugging my code. We use Token based authentication in our app and implemented a lifecycle callback which allows us to perform an instant logout, if the user ends his session on the identity provider. We pass this function as an option for the live_session macro in the router:
def on_mount(:default, _params, session, socket) do
if connected?(socket), do: Accounts.subscribe()
current_user = Accounts.get_user(session["current_user_id"])
locale = if current_user, do: current_user.locale, else: session["locale"]
Gettext.put_locale(Web.Gettext, locale)
socket =
socket
|> assign_new(:locale, fn -> locale end)
|> assign_new(:current_user, fn -> current_user end)
|> attach_hook(:sso_logout_hook, :handle_info, fn
{Core.Accounts, "user-logged-out", payload}, socket ->
if socket.assigns.current_user.origin_id == payload["sub"] do
{:halt, redirect(socket, to: Routes.session_path(socket, :logout))}
else
{:cont, socket}
end
_params, socket ->
{:cont, socket}
end)
{:cont, socket}
end
It works as expected, but not when another module is implementing other :handle_info callbacks. Does anyone know what this problem might be?
What does the “other module” hook look like? Does the other handle_info hook have a catch-all with {:cont, socket} that forwards the message to downstream hooks?
Oh, i’m sorry, with other module I was referring to another LiveView (in this case called MachineLive.Index). There are no additional hooks only the already described one. For example we have implemented multiple handle_info callbacks in this LiveView. One of them is the following:
@impl true
def handle_info({Machines, "machine-changed", payload}, socket) do
socket =
if payload[:account_id] == socket.assigns.current_account.id do
fetch_and_assign_machines(
socket,
socket.assigns.current_account.id,
socket.assigns.params
)
else
socket
end
{:noreply, socket}
end
In this LiveView we receive the logout event via PubSub, but it seems that the previously assigned sso_logout_hook hook is not available at that time. We get the following exception:
FunctionClauseError: no function clause matching in Web.Machines.MachineLive.Index.handle_info/2
File "lib/web/live/machines/machine_live/index.ex", line 97, in Web.Machines.MachineLive.Index.handle_info/2
File "lib/phoenix_live_view/channel.ex", line 276, in Phoenix.LiveView.Channel.handle_info/2
File "gen_server.erl", line 1123, in :gen_server.try_dispatch/4
File "gen_server.erl", line 1200, in :gen_server.handle_msg/6
File "proc_lib.erl", line 250, in :proc_lib.wake_up/3
I was thinking that I can make the ´sso_logout_hook´ for all live views globally accessible when mounted via attach_hook. Maybe I got that wrong
The hook is mounted within the router. This seems to be okay:
live_session :authenticated, on_mount: Web.InitAssigns do
scope "/", Web do
pipe_through [:browser, :ensure_authenticated]
scope "/machines", Machines, as: :machine do
live "/machines", MachineLive.Index, :index
end
end
I checked the if-clause and the condition is matching. Do I need to halt the connection and detach the hook when the else clause is interpreted? There is an example within the documentation about assigning hooks to the handle_event stage. I’ve tried to follow that example.