Search code examples
elixirphoenix-framework

How to require a user to get authenticated in a live view, via a plug or an anternative?


I have this live view wherein I'm trying to do a plug which I've used in other, non-live, parts of my application:

defmodule MyApp123Web.MyLive do
  use MyApp123Web, :live_view

  # TODO
  plug MyApp123Web.RequireAuthentication

===>

** (CompileError): undefined function plug/1 (there is no such import)

Is this a right approach for a live view in the first place? If not, how would it have to be implemented for a live view in a simple way?

My plug is this:

defmodule MyApp123Web.RequireAuthentication do
  import Plug.Conn

  def init(options), do: options

  def call(conn, _opts) do
    user = conn.assigns[:current_user]
    if is_nil(user) do
      |> Phoenix.Controller.redirect(conn, to: "/login")
      |> halt()
    else
      conn
    end
  end
end

update 1

I have a few existing non-live routes, or a scope, that requires authentication already. How to incorporate the live-view ones with those?

  scope "/my", App123Web, as: :my do
    pipe_through [:browser, :csrf, :require_authentication]

    get "/", My.DashboardController, :index
    get "/index", My.DashboardController, :index2

    # ???
    live_session ???? do
      ???
    end

Will a live_session inside this scope even have to be require authentication itself?


Solution

  • As pointed out in this guide, a plug isn't enough for a liveview and you also need to check authentication on mount:

    LiveView begins its life-cycle as a regular HTTP request. Then a stateful connection is established. Both the HTTP request and the stateful connection receive the client data via parameters and session. This means that any session validation must happen both in the HTTP request (plug pipeline) and the stateful connection (LiveView mount).

    As explained in this same article, you can define a reusable mount hook like:

    defmodule MyAppWeb.UserLiveAuth do
      import Phoenix.LiveView
    
      def on_mount(:default, _params, %{"user_id" => user_id} = _session, socket) do
        socket = assign_new(socket, :current_user, fn ->
          Accounts.get_user!(user_id)
        end)
    
        if socket.assigns.current_user.confirmed_at do
          {:cont, socket}
        else
          {:halt, redirect(socket, to: "/login")}
        end
      end
    end
    

    This hook can then simply be used either directly in your live view using Phoenix.LiveView.on_mount/1, or in the router with Phoenix.LiveView.Router.live_session/3:

      live_session :default, on_mount: MyAppWeb.UserLiveAuth do
        live "/foo", FooLive, :index
      end