rhcarvalho

rhcarvalho

How to: Embed a LiveView via iframe

After collecting information from multiple sources (this forum, blogs, StackOverflow and GitHub), I was finally able to successfully embed part of an existing Phoenix LiveView app in an iframe.

I’d like to share the steps here, both for future reference and to hopefully help others. If you find this useful, please let me know. Do you think the official docs should contain a small guide on this topic?


Configuring a LiveView app to be embeddable via iframe

There are luckily only a few changes to be made to a freshly generated app. There are some tradeoffs and considerations to be made, and your requirements might differ from the decisions below.

This guide assumes you only need to expose part of your application, rendering unauthenticated pages (not depending on session cookies), and you want to keep the standard security measures in place.

There are only two parts, let’s begin.

LiveView Socket (endpoint.ex and app.js)

Some threads have suggested changing the default session cookie config from same_site: "Lax" to same_site: "None", secure: true, but that affects all LiveViews and other routes.

Instead, following a suggestion from Chris McCord, use a separate socket for iframe-originated WebSocket connections (and long poll fallback):

# file: my_app/lib/my_app_web/endpoint.ex

  # Default config:
  @session_options [
    store: :cookie,
    key: "_gpt_demo_key",
    signing_salt: "ZfIfSPDp",
    same_site: "Lax"
  ]

  socket "/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [session: @session_options]],
    longpoll: [connect_info: [session: @session_options]]

  # Add new socket specifically for embedded pages. This socket will not
  # Have access to session info, in particular won't be able to read information
  # stored in session cookies such as CSRF token and logged in user token.
  socket "/embed/live", Phoenix.LiveView.Socket, websocket: true, longpoll: true

Update my_app/assets/js/app.js to connect to the correct URL:

let socketUrl = window.location.pathname.startsWith("/embed/") ? "/embed/live" : "/live"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

let liveSocket = new LiveSocket(socketUrl, Socket, {
  longPollFallbackMs: 2500,
  params: {_csrf_token: csrfToken}
})

Pipeline, layouts, routes (router.ex)

Create a new Plug pipeline for the routes that should be embeddable. Possibly not all pages of your app should be allowed within an iframe.

As we’re not passing session information to LiveView as per our Socket config earlier, make sure the root_layout and layout in use don’t depend on session information, for instance they do not refer to @current_user if you use mix phx.gen.auth.

You may want to create specific layouts for the embed pages, which might reuse components from the rest of the app.

The layout can be configured in the live_session declaration in router.ex (Live layouts — Phoenix LiveView v1.2.5).

Example, assuming you created a layout my_app/lib/my_app_web/components/layouts/embedded.html.heex:

# file: my_app/lib/my_app_web/router.ex

  # Default config:
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  # Similar to default `:browser` pipeline, but with one more plug
  # `:allow_iframe` to securely allow embedding in an iframe.
  pipeline :embedded do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :allow_iframe
  end

  # Configure LiveView routes using the `:embedded` pipeline
  # and custom `embedded.html.heex` layout.
  scope "/embed", MyAppWeb do
    pipe_through [:embedded]

    live_session :embedded,
      layout: {MyAppWeb.Layouts, :embedded} do
      live "/:id", EmbeddedLive
    end
  end
  
  # A plug to set a CSP allowing embedding only on certain domains.
  # This is just an example, actual implementation depends on project
  # requirements.
  defp allow_iframe(conn, _opts) do
    conn
    |> delete_resp_header("x-frame-options")
    |> put_resp_header(
      "content-security-policy",
      "frame-ancestors 'self' https://example.com" # Add your list of allowed domain(s) here
    )
  end

For handling only GET requests, it is fine to keep the :protect_from_forgery plug. Other use cases, like requiring form submissions, login, etc, might require further changes.


After trying a few config changes, debugging infinitely reloading pages, and so on, the above is the minimal set of changes that got me to a working state.

If you got here, thanks for reading and hope it was helpful to you, happy coding :purple_heart:

Most Liked

superchris

superchris

It would fit, with some caveats. You would need to port your LiveViews to be LiveState channels instead. The front end templates would need to be rewritten as custom elements. Using a custom element in html requires a script tag to load the code that defines the element, but after that it is usable the same as any other html element, and can be styled with css to the extent you choose. This added flexibility may or may not be worth the level of effort over an iframe, it just depends on your specific situation.

Where Next?

Popular in Guides/Tuts Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
tfwright
I thought I’d share a small project I’m working on to gain some familiarty with LiveView in a Phoenix app. Github Repo Deployment It’s...
New
anuragg
Hi everyone! I’m the founder of Render, a new cloud provider with native support for Elixir. When we launched Elixir support the most po...
New
georgeguimaraes
Another cool plugin for Neovim, GitHub - jmbuhr/otter.nvim: Just ask an otter! 🦦 · GitHub makes it possible to run linters for embedded c...
New
bentanweihao
I wrote a thing: http://engineering.pivotal.io/post/how-to-set-up-an-elixir-cluster-on-amazon-ec2/ Hope this is helpful!
New
danschultzer
I wrote this blog post based on our experiences setting up continuous delivery for our first production umbrella Phoenix app. Coming from...
New
AstonJ
This was originally posted on my blog, but since my I’ve taken it down (I hadn’t posted anything on it for a while and since it was runni...
New
drapermd
So here is the code I came up with to generically generate an array param that will be stored on a jsonb property in ecto. It only handl...
New
mudasobwa
The post covering how to generate nifty types to use in @spec in compile time with macros.
New
kevinlang
Hey all, With Phoenix 1.6 just around the corner, I figured I’d make a tutorial on how to add Bulma to a new Phoenix 1.6 project. By lev...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
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
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
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
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
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
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127536 1222
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement