polypush135

polypush135

Going off the example auth blog post where we have a plug helper like so.

# lib/my_app/router.ex
import MyAppWeb.UserAuth

pipeline :browser do
   ...
  plug :fetch_current_user
end

# lib/my_app_web/controllers/user_auth.ex
@doc """
Authenticates the user by looking into the session
and remember me token.
"""
def fetch_current_user(conn, _opts) do
  {user_token, conn} = ensure_user_token(conn)
  user = user_token && Accounts.get_user_by_session_token(user_token)
  assign(conn, :current_user, user)
end

In the context of a LiveView module, I see the user_token in the session on the mount callback.

I have seen the notes about sessions being serialized into strings, should I be concerned about upstream pipelines that adds this usrer_token to the session? When inspecting my session values on the mount I see a binary for the user token.

"user_token" => <<..,, ..., ..., ..., >>

Also, I’ve seen the notes about storing the user id vs the whole user value because of the string serialization.

After some reading, it looks like the past way of setting up the session from the route no longer works. Remove deprecate features · phoenixframework/phoenix_live_view@0020c35 · GitHub

My other question is do I just make another plug that sets the session with the user_id for the socket?
if so I assume I then will need to query for the user via the user id every request for LiveView and auth. Is there a better way?

Showing Posts 1 to 10

sfusato

sfusato

That’s now the default behaviour (all the session is passed and you don’t need to specify the keys):

When a LiveView is rendered, all of the data currently stored in the connection session (see Plug.Conn.get_session/1 ) will be given to the LiveView.

It is also possible to pass additional session information to the LiveView through a session parameter:

# In the router
live "/thermostat", ThermostatLive, session: %{"extra_token" => "foo"}

# In a view
<%= live_render(@conn, AppWeb.ThermostatLive, session: %{"extra_token" => "foo"}) %>

Notice the :session uses string keys as a reminder that session data is serialized and sent to the client. So you should always keep the data in the session to a minimum. I.e. instead of storing a User struct, you should store the “user_id” and load the User when the LiveView mounts.

You could always add a cache for getting the user with a low refresh ttl value.

ChristopheBelpaire

ChristopheBelpaire

Hello,
I wanted to achieve the same kind of behaviour and be able to access to the current_user in my live view assigns.
The best would be able to assign the current_user to the socket assign, but it doesn’t seems to be possible.
The only way I found to achieve this is to inject a mount function, in live :

  def live do
    quote do
      use BrandManagerWeb.LoadUser
      use Phoenix.LiveView, layout: {BrandManagerWeb.LayoutView, "app.html"}
      alias BrandManagerWeb.Router.Helpers, as: Routes
    end
  end
defmodule BrandManagerWeb.LoadUser do
  alias Core.Accounts
  alias Phoenix.LiveView

  defmacro __using__(_opts) do
    quote do
      def mount(params, %{"user_id" => user_id} = session, socket) do
        session = Map.delete(session, "user_id")

        socket =
          LiveView.assign_new(socket, :current_user, fn ->
            manager_id && Accounts.get_user(user_id)
          end)

        mount(params, session, socket)
      end
    end
  end
end

It is working, but it is kind of a hack :wink:

Is there a better way to achieve this ?
Thanks in advance!

sfusato

sfusato

It’s what the docs suggest, so definitely not a hack (well, minus the fact that you are doing it in a macro, but other than that it’s the same thing).

polypush135

polypush135 OP

I’m mostly asking in the context of this post An upcoming authentication solution for Phoenix - Dashbit Blog

So I already have a pipeline that gets a user from a give user token found in the session.

At this point, my first question would be: Should I make a different pipeline for Controllers vs LiveViews?
If I’m already doing a query to look the user up and put that user whole in my assigns via current_user does this still make sense to do, only to add another plug that plucks the user’s ID only just to have to do it all over again on the call to mount?

Seems to me that I would want a different pipeline that calls fetch_current_user_id for LiveView and fetch_current_user for normal controllers. That way I forgo the query for the user in my LiveView till later in the mount where it needs to happen again anyways.

polypush135

polypush135 OP

That seems to only be true for the first call to mount as the user_token from my session only seems to be available at that time and not the following call to mount.

chrismccord

chrismccord

Creator of Phoenix

You need to pull the user_token from the session and fetch the user in the same way the plug pipeline does. You can make use of assign_new to avoid fetching on the HTTP request, but you need to fallback to fetching on the connected mount, using the same mechanism that the plug pipeline does:

      def mount(_params, %{"user_token" => user_token}, socket) do
        {:ok, 
          socket 
          |> ...
          |> assign_new(:current_user, fn -> 
            Accounts.get_user_by_session_token(user_token)
         end)}
      end

To avoid duplication in your LVs, you could pull this out into an assign_defaults function that you write to wire up common assigns, like current_user. Make sense?

polypush135

polypush135 OP

Still having a little trouble understanding the correct flow.

As stated before I’m working with the same pipeline as the post about auth.

So starting from there my routes look like so.

pipeline :browser do
  ...
  plug :fetch_current_user
end

scope "/", MyAppWeb do
  pipe_through :browser
  
  ...
  live "/posts/:slug", PostLive.Show, :show
end

And I have my view like so.

# /lib/my_app_web/live/post_live/show.ex

def mount(params, %{"user_token" => user_token} = session, socket) do
  # shows user token in the session
  IO.inspect([params, session, socket])

  {:ok,
    assign_new(socket, :current_user, fn ->
      {:ok, user} = MyApp.Accounts.get_user_by_session_token(user_token)
      user
    end)
    # Shows current user in the assigns
    |> IO.inspect()}
end

# Runs the second time for the js call
def mount(params, session, socket) do
  # Does not show user_token or current_user
  IO.inspect([params, session, socket])

  {:ok, socket}
end

I can see from my logs that the first call to mount for the HTTP request looks like so.
I can also see from the IO.inspect from the first call to mount which includes a user_token in the session.

[info] GET /posts/last
[debug] Processing with Phoenix.LiveView.Plug.show/2
  Parameters: %{"slug" => "foobar"}
  Pipelines: [:browser]
[debug] QUERY OK source="users_tokens" db=9.2ms queue=0.1ms idle=7128.2ms
SELECT u1."id", u1."email", u1."hashed_password", u1."confirmed_at", u1."admin", u1."inserted_at", u1."updated_at" FROM "users_tokens" AS u0 INNER JOIN "users" AS u1 ON u1."id" = u0."user_id" WHERE (...)]
[
  %{"slug" => "foobar"},
  %{
    "_csrf_token" => "...",
    "user_token" => ...
  },
  #Phoenix.LiveView.Socket<
    assigns: %{
      flash: %{},
      live_action: :show,
      live_module: MyAppWeb.PostLive.Show
    },
    ...
   >
]

I can also see the IO.inspect at the end of the same pipeline I used to assign_new which shows the current_user being set in the assigns as I would expect.

#Phoenix.LiveView.Socket<
  assigns: %{
    current_user: #MyApp.Accounts.User<
      ...
    >,
    ...
  },
  changed: %{current_user: true},
  ...
>

But when it comes to the second call to mount this is where I get a little lost.
Looking at my logs I see the call to js.

[info] CONNECTED TO Phoenix.LiveView.Socket in 326µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "...", "vsn" => "2.0.0"}
[
  %{"slug" => "foobar"},
  %{},
  #Phoenix.LiveView.Socket<
    assigns: %{
      flash: %{},
      live_action: :show,
      live_module: MyAppWeb.PostLive.Show
    },
    changed: %{},
    ...
  >
]

Here I don’t see the session nor the prior assigns of current_user.

Also I would have also assumed I should not have directly put the whole current_user into the assigns but rather thought I would need to reconcile the user via user_id.

chrismccord

chrismccord

Creator of Phoenix

your second greedy match mount/3 should not be necessary and the fact that you are getting there is definitely the issue. The connected call will include the Plug session , provided you have wired up the session options in your endpoint and csrf token in app.js. Can you post your endpoint? If you are getting here:

# Runs the second time for the js call
def mount(params, session, socket) do
  # Does not show user_token or current_user
  IO.inspect([params, session, socket])

  {:ok, socket}
end

Then it makes sense the current_user is not there, because it was never set. The first clause should have matched, so let’s kill the catch-all and figure out why your plug session isn’t being provided.

polypush135

polypush135 OP

Heres my endpoint.
https://github.com/MorphicPro/morphic.pro/blob/wip/lib/morphic_pro_web/endpoint.ex#L37

And my js
https://github.com/MorphicPro/morphic.pro/blob/wip/assets/js/app.js#L29-L45

Also note this is on my throw away branch wip just incase you try to explore other parts of this repo.

polypush135

polypush135 OP

I am seeing a discrepancy between the _csrf_token from the first and second call to the mount. I assume they should be the same for each?

Edit: Ok I think I found the issue in my endpoint.
I was missing websocket: [connect_info: [session: @session_options]]

Edit #2:
Yeah that was it looks like I’m seeing the user_token in the second call to mount now. :+1:

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews