Missing assigns in application layout

Hello, i upgraded my application to phoenix 1.7.7 Now I implemented the authentication (created by phx generator). In my root layout I have access to the assign @current_user, but not in my application layouts. I already compared my code to a fresh generated project, but I can not find the difference.

This is an example of one of the controllers that has the assign in the root layout, but not in the application layout:

...
  def live_view_admin do
    quote do
      use Phoenix.LiveView,
        layout: {ClubWeb.Layouts, :admin}

      unquote(html_helpers())
    end
  end
...
defmodule ClubWeb.Layouts do
  use ClubWeb, :html

  embed_templates "layouts/*"
end
defmodule ClubWeb.Admin.EventLive.Index do
  use ClubWeb, :live_view_admin

  alias Club.Events
  alias Club.Events.Event

  @impl true
  def mount(_params, _session, socket) do
    {:ok, stream(socket, :events, Events.list_events())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit Event")
    |> assign(:event, Events.get_event!(id))
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "New Event")
    |> assign(:event, %Event{})
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Events")
    |> assign(:event, nil)
  end

  @impl true
  def handle_info({ClubWeb.Admin.EventLive.FormComponent, {:saved, event}}, socket) do
    {:noreply, stream_insert(socket, :events, event)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    event = Events.get_event!(id)
    {:ok, _} = Events.delete_event(event)

    {:noreply, stream_delete(socket, :events, event)}
  end
end

What do you have in your router?

Typically you would have an on mount hook on the live_session which ensures the user is authenticated and sets the current_user on the socket.

Hey andrewh

Here is my router:

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {ClubWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :fetch_current_user
  end

And to mention again: I CAN access to @current_user in the root.html.heex. but not in admin.html.heex which is based on root.html.heex

Besides the pipeline your router should look something like the following with use of live_session and an on_mount hook for liveview authentication handling:

scope "/", MyAppWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]

    live_session :redirect_if_user_is_authenticated,
      on_mount: [{MyAppWeb.UserAuth, :redirect_if_user_is_authenticated}] do
      live "/users/register", UserRegistrationLive, :new
      live "/users/log_in", UserLoginLive, :new
      live "/users/reset_password", UserForgotPasswordLive, :new
      live "/users/reset_password/:token", UserResetPasswordLive, :edit
    end

    post "/users/log_in", UserSessionController, :create
  end

  scope "/", MyAppWeb do
    pipe_through [:browser, :require_authenticated_user]

    live_session :require_authenticated_user,
      on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
      live "/users/settings", UserSettingsLive, :edit
      live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
    end

  scope "/", MyAppWeb do
    pipe_through [:browser]

    delete "/users/log_out", UserSessionController, :delete

    live_session :current_user,
      on_mount: [{MyAppWeb.UserAuth, :mount_current_user}] do
      live "/users/confirm/:token", UserConfirmationLive, :edit
      live "/users/confirm", UserConfirmationInstructionsLive, :new
    end
  end
end

Normal controllers will have session cookies and the user will be set on the conn using the plug, but liveview is based on web sockets so you need the live_session handling and on_mount hook in the router to set @current_user for you otherwise you would have to put that auth logic in the mount callback in each and every live view module.

2 Likes

ok, thanks, this solves the problem

...

  scope "/admin", ClubWeb.Admin, as: :admin do
    pipe_through([:browser, :require_authenticated_user])

    live("/", EventLive.Index, :index)
...

changing to

...
  scope "/admin", ClubWeb.Admin, as: :admin do
    pipe_through([:browser, :require_authenticated_user])

    live_session :adminboard,
      on_mount: [{ClubWeb.UserAuth, :ensure_authenticated}] do
      live("/", EventLive.Index, :index)
...

Thank you very much!!

2 Likes