Pow callback on logout or session expiry?

In the Pow GitHub issue 271, Chris McCord suggests broadcasting disconnect on logout or session expiry in order to disconnect active user connections:

Note that by default since a few versions ago that you can broadcast “disconnect” to the LV socket id to invalidate/kill any active user connections on logout, as long as you add the :live_socket_id to the session on login:

phoenix_live_view/lib/phoenix_live_view.ex at b49e828a15a0121d5cced8691089cadc122eb293 · phoenixframework/phoenix_live_view · GitHub

How could I hook into Pow to do that? Is there a way to get called back when the user logs out or the session expires? Is that what ControllerCallbacks are for?

Here is how I ended up solving it:

defmodule MyAppWeb.Pow.ControllerCallbacks do
  alias Pow.Extension.Phoenix.ControllerCallbacks
  alias Plug.Conn

  @live_socket_id_key :live_socket_id

  def before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, config) do
    user = conn.assigns.current_user
    conn =
      conn
      |> Conn.put_session(:current_user_id, user.id)
      |> Conn.put_session(@live_socket_id_key, "users_sockets:#{user.id}")

    ControllerCallbacks.before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, config)
  end

  def before_respond(Pow.Phoenix.SessionController, :delete, {:ok, conn}, config) do
    live_socket_id = Conn.get_session(conn, @live_socket_id_key)
    BooklistiWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})

    ControllerCallbacks.before_respond(Pow.Phoenix.SessionController, :delete, {:ok, conn}, config)
  end

  defdelegate before_respond(controller, action, results, config), to:  ControllerCallbacks

  defdelegate before_process(controller, action, results, config), to: ControllerCallbacks
end

Then, update config to point to the custom ControllerCallbacks:

config :myapp_web, :pow,
  ...
  controller_callbacks: MyAppWeb.Pow.ControllerCallbacks
2 Likes

You shouldn’t really use controller callbacks for this, controller callbacks are meant for extensions.

I would recommend you to just override the delete function like this

In your router

defmodule MyAppWeb.Router do
...
  scope "/" do
    pipe_through [:browser]

    scope "/", MyAppWeb, as: "pow" do
      delete "/session", SessionController, :delete
    end

    pow_session_routes()
    pow_extension_routes()
  end
end

and create a session controller with a delete function

defmodule MyAppWeb.SessionController do
  @moduledoc """
  Session controller based on https://github.com/danschultzer/pow/blob/master/lib/pow/phoenix/controllers/session_controller.ex
  """
  import Pow.Phoenix.Controller, only: [require_authenticated: 2]
  use MyAppWeb, :controller
  alias Plug.Conn

  plug :require_authenticated when action in [:delete]

  @spec delete(Conn.t(), map()) :: Conn.t()
  def delete(conn, _params) do
    MyAppWeb.Endpoint.broadcast(get_session(conn, :live_socket_id), "disconnect", %{})

    conn
    |> Pow.Plug.delete()
    |> delete_session(:live_socket_id)
    |> redirect(to: Routes.pow_session_path(conn, :new))
  end
end
1 Like

Thanks, @Schultzer, appreciate it!

@Schultzer Thanks for sharing this. I am at the exact point same point, and it solved the delete part for me. Can you share an idea of how a create method has to look like? I am using the extensions PowResetPassword, PowEmailConfirmation and PowPersistentSession in my project.

Best regards
Oliver