Hey! Thanks for all the work you guys do!
TL;DR: due to changes in how the client connects, it is not possible to iframe a live_view app across domains.
Longer Version: Because of business requirements, we have a small app that needs to be iframed across domains. Since cookies inside iframes are not reliable or straight up don't work, it is not possible to successfully connect the client.
To solve the cookie problem, we wrote our own version of Plug.Session. It is very similar to the original, but it does not rely on cookies. Instead, we use other means to get this value and pass it into the store. This is all working fine, until live_view tries to connect.
The main problem we have is that the socket transport assumes the value will be on the cookie, similar to Plug.Session
I'm not sure the implications of changing the socket transport, but if we could modify the way that Phoenix.Socket.Transport.connect_info/3 gets the session, as we did for Plug.Session, we could get around this issue quite easily.
We're also open to any ideas you guys might have.
Thanks
We have struggled with the same problem when we had to embed a LiveView in an existing Rails app and tried to use the Rails cookie og CSRF token so I am curious if there is a way to handle this issue :-)
@cadebward in your case, how are you planning to pass the session value to the LiveView? If you can pass it as a x-header, then you can request x-headers on your socket call (instead of :session) and wrap Phoenix.LiveView.Socket by your own socket implementation that reads the session from x-headers and fetches it up, before ultimately calling Phoenix.LiveView.Socket.
@joerichsen you should be able to get both Rails and Plug stores to play along, see this project.
Thanks @josevalim. Wrapping Phoenix.LiveView.Socket worked great.
@cadebward care to share the code for wrapping the Phoenix.LiveView.Socket ? (lazywebbing a bit here馃檶) - potentially also something for a doc PR..
In endpoint.ex you can point the socket to your own module:
socket "/live", MyAppWeb.LiveViewSocket, websocket: true
Then you just have to setup a channel and the required Phoenix.Socket implementation:
defmodule MyAppWeb.LiveViewSocket do
use Phoenix.Socket
channel "lv:*", Phoenix.LiveView.Channel
@impl Phoenix.Socket
def connect(_params, %Phoenix.Socket{} = socket, _connect_info) do
# fetch your session
session = %{}
{:ok, put_in(socket.private[:session], session)}
end
@impl Phoenix.Socket
def id(socket), do: Phoenix.LiveView.Socket.id(socket)
end
This is where you'd do your session retrieval or what not. Simply passing in a blank map does work, but it foregoes the csrf protections.
Where things get interesting is in connect_info. If you dig down into phoenix sockets you'll eventually find how it works. So, you can pass to your websocket something like this:
socket "/live", MyAppWeb.LiveViewSocket, websocket: [connect_info: [:uri]]
That will then pass uri into your mount/3 function as the last param. We used a combination of these things to solve the problem for our use case.
@josevalim
@joerichsen you should be able to get both Rails and Plug stores to play along, see this project.
I'm trying to do exactly what @joerichsen described, and I'm facing this problem:
Rails throws an exception once LV writes something to _csrf_token

.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/base64.rb:74:in `unpack1'
.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/base64.rb:74:in `strict_decode64'
actionpack (5.2.4.2) lib/action_controller/metal/request_forgery_protection.rb:392:in `real_csrf_token'
actionpack (5.2.4.2) lib/action_controller/metal/request_forgery_protection.rb:321:in `masked_authenticity_token'
actionpack (5.2.4.2) lib/action_controller/metal/request_forgery_protection.rb:308:in `form_authenticity_token'
actionpack (5.2.4.2) lib/abstract_controller/helpers.rb:67:in `form_authenticity_token'
actionview (5.2.4.2) lib/action_view/helpers/csrf_helper.rb:26:in `csrf_meta_tags'
app/views/layouts/application.html.erb:44:in `_app_views_layouts_application_html_erb__4140988955022792088_70207376903020'
actionview (5.2.4.2) lib/action_view/template.rb:159:in `block in render'

This is after trying to update the app to Phoenix 1.5 / LiveVeiw 0.12.1
My initial attempt was with latest 1.4 branch and LV 0.3. It would only read the cookie and never write to it, and Rails worked perfectly fine (since nobody tried to write to its cookie).
I tried doing this:
pipeline :browser do
plug :accepts, ["html"]
...
plug :protect_from_forgery, [session_key: "_phoenix_csrf_token"]
...
end
defp load_csrf_token(endpoint, socket_session) do
if token = socket_session["_phoenix_csrf_token"] do
state = Plug.CSRFProtection.dump_state_from_session(token)
secret_key_base = endpoint.config(:secret_key_base)
Plug.CSRFProtection.load_state(secret_key_base, state)
end
end
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/phoenix/live", Socket, {params: {_phoenix_csrf_token: csrfToken}});
liveSocket.connect();
but with zero success.
Should I stick to 0.3, or there is a way to make Rails and latest Phoenix LiveView coexist happily?
In
endpoint.exyou can point the socket to your own module:socket "/live", MyAppWeb.LiveViewSocket, websocket: trueThen you just have to setup a channel and the required
Phoenix.Socketimplementation:defmodule MyAppWeb.LiveViewSocket do use Phoenix.Socket channel "lv:*", Phoenix.LiveView.Channel @impl Phoenix.Socket def connect(_params, %Phoenix.Socket{} = socket, _connect_info) do # fetch your session session = %{} {:ok, put_in(socket.private[:session], session)} end @impl Phoenix.Socket def id(socket), do: Phoenix.LiveView.Socket.id(socket) endThis is where you'd do your session retrieval or what not. Simply passing in a blank map does work, but it foregoes the csrf protections.
Where things get interesting is in
connect_info. If you dig down into phoenix sockets you'll eventually find how it works. So, you can pass to your websocket something like this:socket "/live", MyAppWeb.LiveViewSocket, websocket: [connect_info: [:uri]]That will then pass
uriinto yourmount/3function as the last param. We used a combination of these things to solve the problem for our use case.
how did you pass the session as an x-header as @josevalim suggested?
@L-Zuluaga we ended up not using it. I don't remember all the nuances but I think you can pass connect_info into the socket, which you can read about in the socket transport docs.
Most helpful comment
In
endpoint.exyou can point the socket to your own module:Then you just have to setup a channel and the required
Phoenix.Socketimplementation:This is where you'd do your session retrieval or what not. Simply passing in a blank map does work, but it foregoes the csrf protections.
Where things get interesting is in
connect_info. If you dig down into phoenix sockets you'll eventually find how it works. So, you can pass to your websocket something like this:That will then pass
uriinto yourmount/3function as the last param. We used a combination of these things to solve the problem for our use case.