Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve cookie access when running in iframe #1888

Merged
merged 2 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 54 additions & 33 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,44 +62,65 @@ function connect() {
}

// When Livebook runs in a cross-origin iframe the browser may restrict access
// to cookies. This is the case in Safari with the "Prevent cross-site tracking"
// option enabled, which is the default. Without cookies access, the session
// is not stored, so CSRF tokens are invalid. Consequently, LV keeps reloading
// the page, as we try to connect the socket with invalid token. To work around
// this we tell the user to open Livebook outside the iframe.
// to cookies. Without cookies access, the session is not stored, so CSRF tokens
// are invalid. Consequently, LV keeps reloading the page, as we try to connect
// the socket with invalid token. To work around this we tell the user to open
// Livebook outside the iframe.
//
// The behaviour varies across browsers and browsing modes (regular and private).
// A few examples (at the time of writing):
//
// * Safari by default blocks all cross-origin cookies. This is controlled by
// the "Prevent cross-site tracking" option
//
// * Chrome in incognito mode blocks all cross-origin cookies, can be relaxed
// on per-site basis
//
// * Firefox implements state partitioning (1) and it is enabled for storage
// by default since Firefox 103 (2). With storage partitioning, the embedded
// site gets a separate storage bucket scoped by the top-level origin, so
// the site generally works as expected
//
// * Brave also implements storage partitioning (3)
//
// To detect whether cookies are allowed, we check for the user data cookie,
// which should be set by the server on the initial request and is accessible
// from JavaScript (without HttpOnly).
//
// Also see the proposal (4), which may streamline this in the future.
//
// (1): https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning#state_partitioning
// (2): https://www.mozilla.org/en-US/firefox/103.0/releasenotes
// (3): https://brave.com/privacy-updates/7-ephemeral-storage
// (4): https://github.com/privacycg/CHIPS

if (document.hasStorageAccess) {
document.hasStorageAccess().then((hasStorageAccess) => {
if (hasStorageAccess) {
connect();
} else {
const overlayEl = document.createElement("div");
if (loadUserData() === null) {
const overlayEl = document.createElement("div");

overlayEl.innerHTML = `
<div class="fixed top-0 bottom-0 left-0 right-0 z-[1000] px-4 py-8 bg-gray-900/95 flex justify-center items-center">
<div class="max-w-[600px] w-full flex flex-col">
<div class="text-xl text-gray-100 font-medium">
Action required
</div>
<div class="mt-3 text-sm text-gray-300">
It looks like Livebook does not have access to cookies. This usually happens when
it runs in an iframe. To make sure the app is fully functional open it in a new
tab directly.
</div>
<div class="mt-6">
<a id="open-app" class="button-base button-blue" target="_blank">
Open app
</a>
</div>
</div>
overlayEl.innerHTML = `
<div class="fixed top-0 bottom-0 left-0 right-0 z-[1000] px-4 py-8 bg-gray-900/95 flex justify-center items-center">
<div class="max-w-[600px] w-full flex flex-col">
<div class="text-xl text-gray-100 font-medium">
Action required
</div>
`;
<div class="mt-3 text-sm text-gray-300">
It looks like Livebook does not have access to cookies. This usually happens when
it runs in an iframe. To make sure the app is fully functional open it in a new
tab directly. Alternatively you can relax security settings for this page to allow
third-party cookies.
</div>
<div class="mt-6">
<a id="open-app" class="button-base button-blue" target="_blank">
Open app
</a>
</div>
</div>
</div>
`;

overlayEl.querySelector("#open-app").href = window.location;
overlayEl.querySelector("#open-app").href = window.location;

document.body.appendChild(overlayEl);
}
});
document.body.appendChild(overlayEl);
} else {
connect();
}
10 changes: 9 additions & 1 deletion assets/js/lib/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ function getCookieValue(key) {
}

function setCookie(key, value, maxAge) {
const cookie = `${key}=${value};max-age=${maxAge};path=/`;
const cookie = `${key}=${value};max-age=${maxAge};path=/${cookieOptions()}`;
document.cookie = cookie;
}

function cookieOptions() {
if (document.body.hasAttribute("data-within-iframe")) {
return ";SameSite=None;Secure";
} else {
return ";SameSite=Lax";
}
}
1 change: 1 addition & 0 deletions lib/livebook_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<body
class="bg-white"
data-feature-flags={Livebook.Config.enabled_feature_flags() |> Enum.join(",")}
data-within-iframe={Livebook.Config.within_iframe?()}
>
<%= @inner_content %>
</body>
Expand Down
20 changes: 17 additions & 3 deletions lib/livebook_web/plugs/user_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,23 @@ defmodule LivebookWeb.UserPlug do
else
user_data = user_data(User.new())
encoded = user_data |> Jason.encode!() |> Base.encode64()
# Set `http_only` to `false`, so that it can be accessed on the client
# Set expiration in 5 years
put_resp_cookie(conn, "lb:user_data", encoded, http_only: false, max_age: 157_680_000)

put_resp_cookie(
conn,
"lb:user_data",
encoded,
# We disable HttpOnly, so that it can be accessed on the client
# and set expiration to 5 years
[http_only: false, max_age: 157_680_000] ++ cookie_options()
)
end
end

defp cookie_options() do
if Livebook.Config.within_iframe?() do
[same_site: "None", secure: true]
else
[same_site: "Lax"]
end
end

Expand Down