Pass in session data to a live view when instigated via a live route?

phoenix
guardian
liveview
#1

Is there any way to pass up session data via a live route to a live view controller? Specifically, I’d like to pass up the current authenticated user.

In my mount call I tried to make the following call:

current_user = Zoinks.Guardian.Plug.current_resource( socket.endpoint )

However, this fails with the following error:

[error] #PID<0.20188.5> running ZoinksWeb.Endpoint (connection #PID<0.20187.5>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /tasks
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function ZoinksWeb.Endpoint.private/0 is undefined or private
        (zoinks) ZoinksWeb.Endpoint.private()
        (guardian) lib/guardian/plug/pipeline.ex:203: Guardian.Plug.Pipeline.current_key/1
        (guardian) lib/guardian/plug.ex:297: Guardian.Plug.fetch_key/2
        (guardian) lib/guardian/plug.ex:127: Guardian.Plug.current_resource/2
        (zoinks) lib/zoinks_web/live/task_live/index.ex:26: ZoinksWeb.TaskLive.Index.fetch/1
        (zoinks) lib/zoinks_web/live/task_live/index.ex:15: ZoinksWeb.TaskLive.Index.mount/2
        (phoenix_live_view) lib/phoenix_live_view/view.ex:332: Phoenix.LiveView.View.do_static_mount/3
        (phoenix_live_view) lib/phoenix_live_view/view.ex:197: Phoenix.LiveView.View.static_render/3
        (phoenix_live_view) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix) lib/phoenix/router.ex:275: Phoenix.Router.__call__/1
        (zoinks) lib/plug/error_handler.ex:64: ZoinksWeb.Router.call/2
        (zoinks) lib/zoinks_web/endpoint.ex:1: ZoinksWeb.Endpoint.plug_builder_call/2
        (zoinks) lib/plug/debugger.ex:122: ZoinksWeb.Endpoint."call (overridable 3)"/2
        (zoinks) lib/zoinks_web/endpoint.ex:1: ZoinksWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:33: Phoenix.Endpoint.Cowboy2Handler.init/2
        (cowboy) /Users/hamishmurphy/Documents/Elixir/zoinks/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /Users/hamishmurphy/Documents/Elixir/zoinks/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
        (cowboy) /Users/hamishmurphy/Documents/Elixir/zoinks/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

That’s because there is no ZoinksWeb.Endpoint.private[:guardian_key]. So it would be helpful if I could just pass up the data from the assigns that is already attached to the View. Is there some way I can do this?

0 Likes

#2

I missed this part in the documentation about referencing parent assigns.

This plug sets the current_user assign before the live view route is instigated:

defmodule Zoinks.Guardian.CurrentUser do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    current_user = Zoinks.Guardian.Plug.current_resource(conn)
    assign( conn, :current_user, current_user )
  end
end

In my live view, the call to assign_new exposes the current_user assign (supplied by the parent view):

def mount(session, socket) do
  {:ok, fetch(assign_new(socket, :current_user, fn -> end))}
end

defp fetch( %{assigns: assigns} = socket ) do
  # Do something with assigns.current_user

  socket
end
0 Likes

#3

You still need to provide the minimal session data necessary to fetch the current user. For example, if the LV disconnects or crashes and rejoins, you won’t have the current_user from the parent assigns, so you still need to pass the current_user.id to the LV session and your mount needs to fallback to fetching the user from the DB in the event of connection/error recovery: ie:

def mount(%{user_id: user_id} = _session, socket) do
  {:ok, assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}
end
1 Like

#4

Is there any way I can add the user_id key to the session without making an explicit call to live_render? This is what I have right now:

defmodule ZoinksWeb.Router do
  pipeline :browser_auth do
    plug Zoinks.Guardian.AuthPipeline
    plug Zoinks.Guardian.CurrentUser
  end

  scope "/", ZoinksWeb do
    pipe_through [:browser, :browser_auth]

    live "/tasks", TaskLive.Index
  end
end

Or do I need to switch out the live route with:

get "/tasks", TaskController, :index

And make the call to live_render like this?

defmodule ZoinksWeb.TaskController do
  def index(conn, _params) do
    live_render(conn, TasksLive.Index, session: %{
      user_id: conn.assigns.current_user.id
    })
  end
end
0 Likes