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
@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
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.
bunnylushington
If you’re looking to auth every route, you can put the auth plug before the Hologram handler in the endpoint.ex and, as you say, use the session for passing data around. This works well:
# the plug
def call(conn, _opts) do
case conn.req_headers |> Map.new() |> JWT.verify_header() do
{:ok, jwt_data} ->
Plug.Conn.fetch_session(conn)
|> Plug.Conn.put_session(:email, jwt_data["email"])
_ ->
conn
|> send_resp(403, "not authenticated")
|> halt()
end
end
with this init in the layout:
def init(_params, component, server) do
email = get_session(server, :email)
put_context(component, :email, email)
end
(Admittedly, Google’s IAP is doing a lot of heavy lifting here and all I have to do is verify the JWT.)
I’m in the position of wanting one page, a health check for K8S, to not require auth. It would be easy enough to carve out a path-based exception but “security” and “exception” don’t always go well together.
sreyansjain
Very nice. That works.
But like you said, with pipelines, it would be so much better.
Exceptions would keep on growing and would quickly become difficult to manage.
I don’t know what @bartblast has thought regarding authentication and authorisation.
Getting to know his take would be very helpful.
sreyansjain
Thank you so much. This really clears things up.
The
setup/3function 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.
This would be great.
Great job with Hologram. More power to you.
garrison
One unfortunate problem with LiveView being built on Phoenix (and therefore Plug) is that they ended up with two separate pipelines (one for the initial request, and then another for the socket). This complicates the developer experience and creates a performance problem (“double render”).
It would be nice if Hologram could avoid making this mistake, though it won’t be easy as there really are two requests under the hood and the framework needs to make it seem like there’s only one. Also it means you can’t rely on Plug the way Phoenix does because you need to abstract it away into the unified pipeline. So it’s a lot of extra work (which is of course why Phoenix ended up this way).







