bunnylushington

bunnylushington

Is it possible to assign a pipeline (in the Phoenix sense) to Hologram routes?

I’m wondering if it’s possible to assign a pipeline (in the Phoenix sense) to Hologram routes. More specifically: I would like all my Hologram pages – but not necessarily pages Phoenix is still routing – to pass through a bespoke MyApp.Plugs.Auth.

Marked As Solved

bartblast

bartblast

Creator of Hologram

@bunnylushington, @sreyansjain

This is definitely something that’s been on my mind as the framework evolves.

Current State:

Right now, Hologram doesn’t have Phoenix-style pipelines, but you can achieve similar functionality by placing auth plugs before the Hologram.Router in your endpoint (as @bunnylushington demonstrated). The Hologram.Router is indeed a Plug that can interoperate with Phoenix sessions, so this approach works well for the current use case.

Planned Solution:

I have a setup/3 function planned for the Roadmap (“Setup Lifecycle Hook - Implement the setup (pre-init) lifecycle hook for components and pages”) that will serve as middleware. The idea is to allow you to include specific implementations of the setup function on different pages through the __using__/1 macro. You could then use directives like:

use MyApp.AdminPage  # instead of use Hologram.Page

Or alternatively:

use Hologram.Page, setup: MyModule.my_fun/2

I’m open to other ideas…

The setup/3 function would receive the regular component and server structs, plus lower-level connection information like headers, and could modify both the server and component structs. This would give you the pipeline-like functionality you’re looking for.

Current Workaround:

For now, you can hook into the endpoint module (as shown in the example in the thread) to handle authentication. Since Hologram when running on top of Phoenix uses Hologram.Router which is a router plug, it can interoperate with Phoenix sessions.

Authentication & Authorization Vision:

Eventually, I want Hologram to have a batteries-included approach for both authentication and authorization. You’d have an Auth module provided by the framework that handles both aspects - you could hook into it with custom auth implementations if you don’t want to use the default. But I want there to be a default auth implementation that works out of the box for both authentication (who you are) and authorization (what you can do).

This is particularly important because when I observed the Phoenix ecosystem, authentication was one of the main things that tripped users up. Since Hologram’s approach is so unusual (automatically transpiled Elixir to JS), this creates some non-standard problems we have to think about - the client-side code is in essence public, so we need to be especially careful about how we handle sensitive authentication logic and ensure that authorization checks happen server-side. It’s crucial to provide clear guard rails and sensible defaults to help users get started quickly without getting overwhelmed by choices or accidentally exposing security vulnerabilities.

Third-Party Auth Integration:

The framework will make it easy to use third-party authentication and authorization solutions. Since Hologram’s auth primitives will be designed to be pluggable, you’ll be able to seamlessly integrate with existing auth libraries and services while still benefiting from Hologram’s built-in auth features for UI components and authorization checks.

Commands and Authorization:

Until we have the Auth primitives provided by the framework, the server struct provides access to session (interoperable with Phoenix) and cookies, and you can implement your custom authorization handlers or use some auth library for now.

The setup function is definitely high on the priority list - it should solve the pipeline problem elegantly while maintaining Hologram’s philosophy of keeping things simple and composable.

Let me know what you think…

Also Liked

garrison

garrison

I was only referring to API design, of course everything in your post is correct.

I think forcing developers to design their LiveViews to perform twice as much work as necessary on initial render is clearly a design mistake. I understand why it worked out this way, and I am definitely not criticizing anyone. But I don’t see how you could argue this is a good design. Since Hologram is a clean slate I am just suggesting that @bartblast avoid digging himself into the same hole.

This is interesting, but to be clear I am referring to the divergent pipelines in the Phoenix and LiveView APIs. Essentially all of this seems wrong to me.

I’m not sure a unified Plug abstraction would solve this problem because the navigation-over-socket which the live_session abstraction is used for is happening in Phoenix, right? I would think a Plug socket pipeline abstraction would only affect the initial socket connection, not the messages flowing over it (which are actually navigation but how would it know).

Not to mention solving the double render requires keeping a LiveView process alive for N seconds after the HTTP request in case the socket comes in. I don’t know these internals in detail but something tells me this would be harder than it sounds (or it would have been done by now). I can think of several other problems that would come up (and I’m sure you can think of even more).

This is just one of those cases where coupling your API to a lower-level abstraction like this creates trouble down the road. I know there was a thread about whether Hologram should be an “independent” framework so I had that in mind as well. I am not necessarily suggesting Hologram shouldn’t use Plug/Phoenix, by the way, just that it may be better not to expose them to end-users in the way that Phoenix exposed Plug.

garrison

garrison

You can’t skip loading on the initial render unless you are okay with that content a) popping in and b) not being present in the dead render (bad for SEO and bots and HN readers who claim to browse with JS disabled and so on). The problem with the pop-in is that it obviates one of the best “advantages” of LiveView, which is on-by-default SSR with no extra work.

Likewise you can’t skip loading on the live render because you need the assigns to actually run the LiveView.

The actual solution is that the assigns from the dead render need to carry on to the live render. One thing you could do is cache your queries so that the live render queries hit the cache. You could go further and cache the assigns specifically somehow.

But what should really be happening is that there should only ever be one LiveView process, with one set of assigns, which survives the dead render, and then the live render should be routed back to it when the socket is opened. A lot of things would have to be done to make that work and I’m not trying to make the claim that it would be easy, but from the API side I think it’s obviously the best design.

Async assigns are that API and are a fantastic solution to this exact problem. I really like async assigns, it’s very clear that a lot of care was put into handling the annoying edge cases properly. Honestly they might be the best-designed API in LiveView. Probably because they seem to have come directly from Chris dogfooding LiveView at Flyio.

But they don’t solve the double render, you would get pop-in.

garrison

garrison

Thanks for this, I had not seen this issue. Unsurprisingly Jose had a good idea of the problems to be solved long before I did :slight_smile:

I should clarify that I think there are two separate problems here:

  • The double render
  • The duplicated API paths for dead/live renders (e.g. plug pipeline and live_session)

The first is a performance problem which requires optimization. The double render can never be fully removed, only optimized away most of the time. And that’s okay, I think.

The second is a UX problem which is downstream of the way LiveView was designed: piece-by-piece on top of an existing framework. There is no reason to force developers to define this logic twice. Even if it has to be applied twice under the hood, I see no reason to leak that into the API. Especially in a security context, where mistakes are expensive!

IMO the second issue is much more egregious than the first. Maybe it can be fixed in LiveView, maybe not. But Hologram is a clean slate so this whole thing can be avoided.

Where Next?

Popular in Questions Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
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
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
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
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New

We're in Beta

About us Mission Statement