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

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

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

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

sreyansjain

Thank you so much. This really clears things up.

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.

This would be great.

Great job with Hologram. More power to you.

garrison

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).

Where Next?

Popular in Questions Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
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
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
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
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
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
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
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
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 27727 240
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement