Price the Storage Access API, and stop calling the header path cookie-free
Two more from the review. The section declared that no frontend change could bring OAuth inside the criterion, which writes off the Storage Access API: an embedded frame can ask the browser, on a user gesture, for access to its own unpartitioned first-party cookies, and both Safari and Chrome implement it. That would read the cookie the callback already set with no backend work, at the cost of a revocable permission prompt. It belongs in the comparison, and it is the only one of the three routes that is not a backend feature. And the constraint bullet still offered CHIPS or a token not relying on ambient cookies as alternatives, when the header path relies on one as soon as the page reloads. Both routes rest on the same attribute; they differ over who writes the cookie and what that costs.
This commit is contained in:
@@ -39,8 +39,8 @@ cookies. Nothing short of the reload proves it: the widget holds a token in memo
|
||||
page, so a test that signs in and posts without reloading passes while the persistence is entirely
|
||||
broken.
|
||||
|
||||
OAuth sits outside that criterion as the flow is built today, and no frontend change brings it
|
||||
inside. The provider callback is a top-level navigation in a popup, so `Service.Set` writes its
|
||||
OAuth sits outside that criterion as the flow is built today. The provider callback is a top-level
|
||||
navigation in a popup, so `Service.Set` writes its
|
||||
cookie there as an ordinary first-party cookie for the auth host, carrying no `Partitioned` and so
|
||||
having no partition key at all. The frame on `food.com` sees it only as an unpartitioned third-party
|
||||
cookie, which is exactly what a blocking browser refuses. The cookie is `HttpOnly` besides, so the
|
||||
@@ -48,14 +48,26 @@ popup cannot read it and hand it over in script. `SameSite` is not what obstruct
|
||||
separate-domain manual has operators set `AUTH_SAME_SITE=none`, and the callback navigation is
|
||||
top-level regardless.
|
||||
|
||||
Bringing OAuth inside the criterion needs a server-mediated handoff, where the popup posts a
|
||||
one-time code to its opener and the frame redeems it over XHR. Two shapes exist and they cost
|
||||
differently. If the redemption answers with `Set-Cookie`, that cookie has to carry `Partitioned` to
|
||||
Two routes could bring OAuth inside the criterion, and both need costing before either is called
|
||||
mandatory.
|
||||
|
||||
The first is the Storage Access API, which exists for exactly this shape: an embedded frame asks
|
||||
the browser, with a user gesture, for access to its own unpartitioned first-party cookies, and
|
||||
Safari and Chrome both implement it. That would let the frame read the cookie the callback already
|
||||
set, with no backend work at all. It is not free, since it spends a permission prompt and the
|
||||
grant is revocable and scoped, but it is a real option and this section previously wrote it off by
|
||||
claiming no frontend change could help.
|
||||
|
||||
The second is a server-mediated handoff, where the popup posts a one-time code to its opener and
|
||||
the frame redeems it over XHR. Two shapes exist there and they cost differently. If the redemption
|
||||
answers with `Set-Cookie`, that cookie has to carry `Partitioned` to
|
||||
be stored at all, which is the upstream library work described below. If it answers with `X-JWT`
|
||||
instead, `fetcher.ts` writes the partitioned pair itself and nothing upstream has to change, which
|
||||
makes it the cheaper of the two. Either is a backend feature and out of scope here. Until one
|
||||
exists, an operator who needs OAuth off-domain serves remark42 from the same registrable domain as
|
||||
the site, or accepts that OAuth readers sign in on the instance's own origin.
|
||||
makes it the cheaper of the two. Both handoff shapes are backend features and out of scope here; the
|
||||
Storage Access
|
||||
route is not, and is the one worth pricing first for that reason. Until something lands, an
|
||||
operator who needs OAuth off-domain serves remark42 from the same registrable domain as the site,
|
||||
or accepts that OAuth readers sign in on the instance's own origin.
|
||||
|
||||
**Where that stands.** Master satisfies the criterion today, provided `AUTH_SEND_JWT_HEADER` is on.
|
||||
The server returns the token in `X-JWT` and `fetcher.ts` writes the `JWT` and `XSRF-TOKEN` cookies
|
||||
@@ -131,8 +143,12 @@ Constraints on any frontend design:
|
||||
outright and honours only cookies explicitly marked `Partitioned`; its CHIPS support has been
|
||||
switched on, off and on again across releases, so pin the current state before relying on a
|
||||
version number. A design depending on `SameSite=None` surviving has an expiry date
|
||||
- the endpoint is CHIPS (`SameSite=None; Secure; Partitioned`) or a token not relying on ambient
|
||||
cookies. Neither is a flag flip, and the first cost is upstream rather than here:
|
||||
- the endpoint is CHIPS (`SameSite=None; Secure; Partitioned`) either way, and the only question is
|
||||
who writes the cookie. Calling the header path a token that does not rely on ambient cookies was
|
||||
wrong: it holds the token in memory for one page, and after a reload the browser sends the
|
||||
partitioned cookie the frontend wrote, so both routes stand on the same attribute and differ only
|
||||
in whether the server or the frontend sets it, plus the XSS cost the frontend writer carries.
|
||||
Neither is a flag flip, and the first cost is upstream rather than here:
|
||||
`go-pkgz/auth/v2@v2.2.0` cannot emit `Partitioned` at all. Both cookies are hand-built in
|
||||
`Service.Set` with only `HttpOnly`, `Path`, `Domain`, `MaxAge`, `Secure` and `SameSite`, and
|
||||
`AUTH_SAME_SITE` in `cmd/server.go` offers `default`/`none`/`lax`/`strict` with no partitioned
|
||||
|
||||
Reference in New Issue
Block a user