Phx.gen.auth: on_mount example from hexdoc not working

Phoenix 1.6.6
Phoenix Liveview 0.17.6

I have had issues with phx.gen.auth since upgrading to 1.6.6. I have a LiveView behind the :require_authenticated_user wall in my router. My understanding was that a redirect to this LiveView would go through the router and redirect first to login (which worked on 1.5). But that has not been happening on 1.6. Instead, the mount is called BEFORE a login is implemented and I get a run time error because the mount is assigning the current_user thinking that the login has already been forced.

So I read the Phoenix LiveView hexdox on “Security considerations of the LiveView.” It recommends implementing on_mount to implement the same verifications as the router.

So I implemented the on_mount EXACTLY like the hexdoc example.

NOTE TO WHOEVER MANAGES THIS HEXDOC: I am very new and inexperienced with Phoenix LiveView. It would be really really helpful if you could update the hexdoc to indicate that UserLiveAuth is NOT the same as UserAuth. I struggled for a day because I initially implemented on_mount in my existing UserAuth file thinking it was the same file but just named differently. I got numerous ambiguity compilation errors because functions like “redirect” are in both Phoenix.LiveView as well as Phoenix.Controller.

I finally figured out that I had to create a new file in MyAppWeb/controllers that was UserLiveAuth. I then implemented exactly what is in the hexdoc example. Here is the code:

defmodule MyAppWeb.UserLiveAuth do
  import Phoenix.LiveView
  alias MyAppWeb.Users

  def on_mount(:default, _params, %{"user_id" => user_id} = _session, socket) do

    socket = assign_new(socket, :current_user,
      fn -> Users.get_user!(user_id)
    end)

    if socket.assigns.current_user.confirmed_at do
      {:cont, socket}
    else
      {:halt, redirect(socket, to: "/users/log_in")}
    end
  end
end

In my LiveView, I added this (just like hexdoc example):

defmodule MyAppWeb.ResourceformLive.ManageResources do
  use MyAppWeb, :live_view
  on_mount MyAppWeb.UserLiveAuth
...
end

RUN TIME ERROR:

(FunctionClauseError) no function clause matching in MyAppWeb.UserLiveAuth.on_mount/4

Any help would be most appreciated.

Is there a user_id in the session map? That is, have you put one there from a dead view before redirecting to the liveview?

The error usually shows you the call you’ve made with the variables it attempted.

1 Like

Initially there is no user_id because the user has not logged in. Users are allowed to look at resources without logging in. But if they want to save resources, then they must log in.

So in the case where the user is not logged in, they click on the button to manage their resources and on_mount or the router should check to see if they are logged in. If not, then they get sent to the login page before being redirected to the manage_resource liveview.

But after that, when they return to the manage_resource liveview, then there will be a user_id because they will be logged in. At that point, the on_mount will find a user_id.

Here is the call when the error comes up. This is based on a user not being logged in and trying to access the liveview that requires a log in (so no user_id is registered yet). I chopped the token and session_id down.

** (FunctionClauseError) no function clause matching in MyAppWeb.UserLiveAuth.on_mount/4
    (myapp_links 0.1.0) lib/myapp_links_web/controllers/User_live_auth.ex:16: MyAppWeb.UserLiveAuth.on_mount(:default, %{}, %{"_csrf_token" => "333”, "session_id" => “333”}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, flash: %{}, live_action: :index}, endpoint: MyAppWeb.Endpoint, id: "phx-FtGg9p3rJPBrcQHD", parent_pid: nil, root_pid: #PID<0.723.0>, router: MyAppWeb.Router, transport_pid: #PID<0.714.0>, view: MyAppWeb.ResourceformLive.ManageResources, ...>)

I get a run time error when the user is logged in. Here is that error:

[error] #PID<0.770.0> running MyAppWeb.Endpoint (connection #PID<0.748.0>, stream id 5) terminated
Server: localhost:4000 (http)
Request: GET /manage-resources
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in MyAppWeb.UserLiveAuth.on_mount/4
        (myapp_links 0.1.0) lib/myapp_links_web/controllers/User_live_auth.ex:16: MyAppWeb.UserLiveAuth.on_mount(:default, %{}, %{"live_socket_id" => "Users_sessions:qDpNiaRqOqES6uLGKKrXPFHBWQSUNlhlb7vaia-qfP4=", "session_id" => "a9de355e-103d-4095-9b2f-5da81310696a", "User_token" => <<bunch of numbers >>}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, flash: %{}, live_action: :index}, endpoint: MyAppWeb.Endpoint, id: "phx-FtGhjoyvWwC7BwLB", parent_pid: nil, root_pid: nil, router: MyAppWeb.Router, transport_pid: nil, view: MyAppWeb.ResourceformLive.ManageResources, ...>)

You’ll need specific code to handle the “no user_id in the session” case, as the on_mount you’ve defined will fail to match (and give the error you’re seeing)

1 Like

Unfortunately, it fails even when the user is logged in.

Is there an example anywhere of how to handle a redirect to a liveview that requires the user to be logged in? All of this worked in 1.5 and I have been really struggling with this upgrade because there are no good working examples anywhere. I took the Pragmatic Studio course, but all of that course is based on 1.5 and the old LiveView. I am contemplating just going back to 1.5. I just can’t find any resources for 1.6 when things don’t work and I’m too inexperienced to troubleshoot these things. I’m desperate for working examples that I can learn from.

SOLUTION:

Define on_mount in UserLiveAuth (NOT UserAuth) as follows:

def on_mount(:default, _params, session, socket) do

    socket =
         assign_new(
              socket,
             :current_user,
             fn -> Users.get_user_by_session_token(session["user_token"])
        end )

    if socket.assigns.current_user == nil do
      {:halt, redirect(socket, to: "/users/log_in")}
    else
      {:cont, socket}
    end
  end
3 Likes

You can look at live_beats app:

  1. UserAuth
  2. RedirectController
  3. Router
2 Likes