hourram

hourram

A few questions on assigns and assign_new()

Hello,

I am working on a web app with Phoenix + Liveview.

I have an authenticated live_session in my router.ex

live_session :dashboard,
  on_mount: [MyappWeb.RouteInfo, MyappWeb.UserLiveAuth, MyappWeb.LiveAdminSidebar] do
    scope "/dashboard", MyappWeb do
      pipe_through [:browser, :require_authenticated_user]

      live "/", WelcomeLive.Index, :index

      scope "/settings", SettingsLive do
        live "/account", Account, :edit
        live "/account/confirm_email/:token", Account, :confirm_email
      end

      scope "/users", UserLive do
        live "/", Index, :index
        live "/new", Index, :new
        live "/:id/edit", Index, :edit

        live "/:id", Show, :show
        live "/:id/show/edit", Show, :edit
      end

      scope "/roles", RoleLive do
        live "/", Index, :index
        live "/new", Index, :new
        live "/:id/edit", Index, :edit

        live "/:id", Show, :show
        live "/:id/show/edit", Show, :edit
      end
    end
  end

I use the on_mount to handle authentication and authorization as described in this doc page: Security considerations of the LiveView model

Here is my UserLiveAuth on_mount function

def on_mount(:default, _params, %{"user_token" => user_token} = _session, socket) do
    socket = socket
    |> assign_new(:current_user, fn -> Accounts.get_user_by_session_token(user_token) end)
    |> assign_new(:user_return_to, fn -> &Routes.welcome_index_path(&1, :index) end)
    |> assign_current_user_access_for_routes()

    cond do
      !is_user_logged_in(socket) ->
        {
          :halt,
          socket
          |> put_flash(:error, "You must log in to access this page.")
          |> redirect(to: Routes.user_session_path(socket, :new))
        }

      !does_user_have_required_capability_for_current_view(socket) ->
        {
          :halt,
          socket
          |> put_flash(:error, "Your current role does not allow you to access this page.")
          |> push_redirect(to: socket.assigns.user_return_to.(socket))
        }

      true ->
        {
          :cont,
          assign(socket, :user_return_to, socket.assigns.current_route_info.menu_item.path)
        }
    end
  end

Here is what I’m trying to achieve.
When the authenticated user tries to go to a page he is not allowed to visit (2nd cond), I want to redirect him to the :user_return_to which would be the last valid page he was on.
So I tried to store the current page on the :cont case (3rd cond), but I realised that the assigns are not persisted after a live_redirect.

Is it a normal behaviour?

If so, I am not sure of the purpose of assign_new over assign.
Here is what is explained in the security considerations of the LiveView model

[assign_new] is a convenience to avoid fetching the current_user multiple times across LiveViews

In my case current_user is fetched everytime I navigate to a new LiveView of the same live session (using live_redirect links).

Is assign_new only useful when the socket connection is established?

How could I do to store the :user_return_to across different LiveViews? Use localStorage?

Thanks in advance for your help.

Marked As Solved

03juan

03juan

This part of the security considerations doc might clear up the confusion

If you perform user authentication and confirmation on every HTTP request via Plugs, such as this:

plug :ensure_user_authenticated
plug :ensure_user_confirmed

And from live_redirect/2

The current LiveView will be shut down and a new one will be mounted in its place, without reloading the whole page. This can also be used to remount the same LiveView, in case you want to start fresh. If you want to navigate to the same LiveView without remounting it, use live_patch/2 instead.

If you are already setting the user through a plug on first page load, then assign_new in the on_mount will ensure that you don’t hit the database again. But when you live_redirect to another live view, the framework kills the previous live view’s process and starts a new one without hitting the plug. In that case the assign will be empty, and assign_new has to fetch the user again because the plug isn’t being run.

I think this part of the security consideration doc is misleading and probably needs clarification.

We use assign_new/3. This is a convenience to avoid fetching the current_user multiple times across LiveViews.

Also Liked

kartheek

kartheek

@hourram Welcome to elixir community.

assign_new(socket_or_assigns, key, fun)
Assigns the given key with value from fun into socket_or_assigns if one does not yet exist.

assign_new/3 assigns only when the key does not exists in socket_or_assigns.

assign/3 just assigns the value every time it is called.

What it means is - assign_new/3 is used so :current_user key is not assigned every time on_mount is called. It is assigned only if :current_user key does not exist in socket.

ani

ani

assign_new won’t run the function if the key already exists. So, for example, if you need to fetch the current user from the DB, if you use assign_new it will query the DB only once and store the value. Even when different live views are mounted it will not query the DB.

ani

ani

Tried it now and yes, it does looks like the function isn’t run when :current_user is set in the conn. I had tried earlier by just logging socket.assigns and in that, current_user wasn’t set, so I assumed the function will be run. Thanks for the clarification!

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29603 241
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement