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

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

voltone
The EEF’s Security WG has released the first public draft of the Secure Coding and Deployment Hardening Guidelines for BEAM languages. “...
New
zazaian
I recently generated the boilerplate for a new mix app using the Phoenix 1.3.1 phx.new generator and realized that the structure of the w...
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
crockwave
To integrate dropdown menus in a Phoenix Liveview app, you can use a combination of js, Hooks, CSS and your .leex and .ex code. You can...
New
smpallen99
Some advice for Elixir programmers. I was reviewing someone's Elixir Code yesterday and found a deadlock condition bug in a GenServer i...
New
kokolegorille
Hello dear alchemists, There was this question some days ago here about the deployment to a VPS. As I was in the process of deploying t...
New
wfgilman
I'm writing up this quick "How to" because what I thought was going to be an easy implementation of a Plug to validate a webhook request ...
New
nelsonic
When we were figuring out how to use Phoenix LiveView we got stuck a few times. So in order to save other people time, we created a comp...
New
lukertty
Install web-mode and mmm-mode first and put this in your config file: (require 'mmm-mode) (require 'web-mode) (setq mmm-global-mode 'may...
New
mudasobwa
The post covering how to generate nifty types to use in @spec in compile time with macros. https://rocket-science.ru/hacking/2020/07/15/...
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
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
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
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
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
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
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
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
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
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

We're in Beta

About us Mission Statement