From 3286f028e38244abc54ee551e2b32e6d2c28a907 Mon Sep 17 00:00:00 2001 From: Dmitry Verkhoturov Date: Mon, 24 Aug 2026 00:03:49 +0100 Subject: [PATCH] Document what each browser actually does with cross-domain auth (#2222) Measured on real domains over real certificates, Remark42 on one registrable domain and the host page on another, with a control cookie behind every blocked column so a run that blocks nothing cannot report a pass. Three results the manual did not carry. Safari blocks third-party cookies out of the box, so AUTH_SAME_SITE=none on its own has already stopped working there, which makes the old recipe broken today and not deprecated later. Firefox reaches a working session by a weaker route than Chrome and Safari do: it accepts the server's attribute-less cookie, and because that cookie is HttpOnly the browser then forbids the widget's script from replacing it, so the session rides on an ordinary unpartitioned third-party cookie even with the header flag on. And Firefox's block-all setting discards partitioned cookies too, so no configuration survives it. Two parameter descriptions were wrong in ways that matter here. AUTH_SAME_SITE default emits no SameSite attribute rather than Lax, which is precisely what lets the widget's own cookie land on Chrome and Safari. And AUTH_TTL_COOKIE does not govern the cookie that carries the session under the header flag, since the frontend hardcodes 200h to mirror the default. --- .../docs/configuration/parameters/index.md | 4 +-- .../docs/manuals/separate-domain/index.md | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/site/content/docs/configuration/parameters/index.md b/site/content/docs/configuration/parameters/index.md index 12239f7f..bdadc250 100644 --- a/site/content/docs/configuration/parameters/index.md +++ b/site/content/docs/configuration/parameters/index.md @@ -78,9 +78,9 @@ services: | image.resize-width | IMAGE_RESIZE_WIDTH | `2400` | width of a resized image | | image.resize-height | IMAGE_RESIZE_HEIGHT | `900` | height of a resized image | | auth.ttl.jwt | AUTH_TTL_JWT | `5m` | JWT TTL | -| auth.ttl.cookie | AUTH_TTL_COOKIE | `200h` | cookie TTL | +| auth.ttl.cookie | AUTH_TTL_COOKIE | `200h` | TTL of the server-set auth cookie. Note it does not govern the cookie the frontend writes under `auth.send-jwt-header`, which is fixed at 200h | | auth.send-jwt-header | AUTH_SEND_JWT_HEADER | `false` | also send JWT as a header, so the frontend can store it in a client-side cookie that survives third-party cookie blocking; the server-set cookies are still sent. [See security considerations](#security-considerations-for-authsend-jwt-header). | -| auth.same-site | AUTH_SAME_SITE | `default` | set same site policy for cookies (`default`, `none`, `lax` or `strict`) | +| auth.same-site | AUTH_SAME_SITE | `default` | SameSite attribute for the server-set auth cookies (`default`, `none`, `lax` or `strict`). `default` emits no attribute at all and leaves the choice to the browser, which is not the same as `lax` | | auth.apple.cid | AUTH_APPLE_CID | | Apple client ID (App ID or Services ID) | | auth.apple.tid | AUTH_APPLE_TID | | Apple service ID | | auth.apple.kid | AUTH_APPLE_KID | | Apple Private key ID | diff --git a/site/content/docs/manuals/separate-domain/index.md b/site/content/docs/manuals/separate-domain/index.md index 53e1bbec..534043df 100644 --- a/site/content/docs/manuals/separate-domain/index.md +++ b/site/content/docs/manuals/separate-domain/index.md @@ -33,7 +33,9 @@ The `'self'` in `ALLOWED_HOSTS` value means "domain where Remark42 is installed `ALLOWED_HOSTS` sets CSP [frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors), which, once enabled, limits the domains where Remark42 would work. The default value is `*` so that it would work on any domain. -`AUTH_SAME_SITE` sets the [SAME_SITE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) attribute for authorisation cookies, allowing Remark42 either on the original domain and subdomains there (default value, which equals to `Lax`) or allows setting authorisation cookies on any domain where remark42 is shown (`None` setting). +`AUTH_SAME_SITE` sets the [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) attribute on the cookies the server sets. `none` lets those cookies be set on any domain where Remark42 is shown. + +The `default` setting does not mean `Lax`. It means Remark42 emits no `SameSite` attribute at all and leaves the choice to the browser, and browsers differ: Chromium treats a missing attribute as `Lax` and so refuses the cookie cross-site, while Firefox accepts it. That difference is not a detail, because it decides which of the two cookie pairs below a reader actually ends up with. `SameSite=None` is not sufficient on its own, and with `AUTH_SEND_JWT_HEADER` it is not necessary either. A browser that blocks third-party cookies drops a cookie set by Remark42 for a reader on another domain no matter what its `SameSite` value is, unless the cookie is explicitly marked [`Partitioned`](https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies), and the server-set cookies are not. `AUTH_SEND_JWT_HEADER=true` is what closes that: the token comes back in an `X-JWT` response header and the widget stores it in its own cookie, written from inside the embedded frame and marked `SameSite=None; Secure; Partitioned`, so the browser keeps it for that embedding site and sends it back after a reload. That cookie is the widget's own doing and owes nothing to `AUTH_SAME_SITE`, which reaches only the pair the server sets. @@ -41,6 +43,37 @@ A browser that refuses a cross-site `Set-Cookie` lacking `SameSite=None` refuses Note that this applies to Email, Telegram and anonymous authorisation, which the widget performs from inside the frame. It does not rescue oAuth, which completes in a popup that is a top-level page of its own, so the cookie set there belongs to the Remark42 domain and the embedded frame never sees it. +### What each browser actually does + +Measured on real domains over real certificates, with Remark42 on one registrable domain and the +host page on another, signing in and then reloading. Every "blocked" column below was verified with +a control cookie: an ordinary third-party cookie written from inside the widget frame has to be +dropped, or the run is not blocking anything and proves nothing. + +| configuration | Chrome, default | Chrome, third-party cookies blocked | Firefox, default | Firefox, "block all third-party" | Safari | +| --- | --- | --- | --- | --- | --- | +| `AUTH_SEND_JWT_HEADER` only | works | works | works | fails | works | +| `AUTH_SAME_SITE=none` only | works | fails | works | fails | fails | +| both | works | works | works | fails | works | + +Three things in that table are worth spelling out. + +**Safari needs no configuring to break the old recipe.** It blocks third-party cookies out of the +box, so `AUTH_SAME_SITE=none` on its own has already stopped working there for every reader. This +is not a future deprecation to plan for. + +**Firefox reaches "works" by a different route, and a weaker one.** Chrome and Safari refuse the +server's cookie when it carries no `SameSite` attribute, which leaves the field clear for the +widget to write its own partitioned pair. Firefox accepts that cookie, and because the server's +`JWT` is `HttpOnly`, the browser then refuses to let the widget's script overwrite it: a cookie set +by JavaScript may not replace an `HttpOnly` one of the same name. So on Firefox the session rides +on an ordinary unpartitioned third-party cookie even with the header flag on, and it disappears the +moment the reader blocks those. + +**No configuration survives Firefox's "block all third-party cookies" setting.** That mode discards +partitioned cookies too, so the `Partitioned` escape hatch does not apply. A reader who has turned +it on cannot stay signed in on an embedded widget, and nothing in Remark42 can change that. + Here are all possible combinations of these two: - Default setup with unaltered variables: comments are shown on any domain, but the authorisation wouldn't work anywhere, except on the same domain Remark42 is installed on and subdomains of it.