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.
This commit is contained in:
Dmitry Verkhoturov
2026-08-23 18:03:49 -05:00
committed by GitHub
parent c947a06d48
commit 3286f028e3
2 changed files with 36 additions and 3 deletions
@@ -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 |
@@ -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.