hypothesis / client

The Hypothesis web-based annotation client.
Other
626 stars 194 forks source link

Empty login window on pages that use `Cross-Origin-Opener-Policy: same-origin` #5769

Open cdrini opened 10 months ago

cdrini commented 10 months ago

Neither the bookmarklet or the Chrome extension works on https://simonwillison.net/2023/Aug/27/wordcamp-llms/ . Tested both Firefox and Chrome.

The UI appears, but it can't log in. Here are the errors in the console:

Upon clicking loading hypothesis:

image

Upon clicking Log in:

image

and the hypothesis UI displays "Error: Failed to open login window". The login window does open, but it's empty

image

The login window displays the following errors:

image

cdrini commented 10 months ago

Oh it started working now! Must've been a fluke

robertknight commented 10 months ago

This is actually a valid issue. I can reproduce in Safari. The problem is that the page sets a cross-origin-opener: same-origin header, which prevents the login popup window from interacting with the sidebar via window.opener.

curl -I 'https://simonwillison.net/2023/Aug/27/wordcamp-llms/'
HTTP/2 200 
date: Mon, 28 Aug 2023 16:31:05 GMT
content-type: text/html; charset=utf-8
x-content-type-options: nosniff
referrer-policy: same-origin
cross-origin-opener-policy: same-origin
via: 1.1 vegur
cf-cache-status: HIT
age: 2572
last-modified: Mon, 28 Aug 2023 15:48:13 GMT
accept-ranges: bytes
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=NtE%2FsXX6eW%2BJYuODgLSAOxAtOqAqac92g8PSWfwXVAdlivrkcyXASodXhrTjhVTr3OmME0wOeRQVF42PvM83%2B42zMKcRBOPa%2BRr5Sfk91s1nZu1vjCivA0gDJ2xRfK7rmq5nPw%3D%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 7fddf14cc9cf71b6-LHR
alt-svc: h3=":443"; ma=86400

There are more details in https://github.com/hypothesis/viahtml/issues/353#issuecomment-1136055492 and https://github.com/hypothesis/viahtml/issues/333#issuecomment-1100047416. At the time those comments were written we were waiting on new web standards to provide a solution for use cases like ours (sigh). Relevant bit on Hypothesis-specific workarounds:

The a site sets the Cross-Origin-Opener-Policy header this can however prevent logging in to from either the bookmarklet or browser extension. Since Chrome has not yet blocked third-party cookies, a workaround for extension users is to visit some other page first, login there, and then re-visit USA Today (or the other page using COOP). A workaround for bookmarklet users is to use Via.

The particular page you mentioned might change at some future point so that it no longer sets this header, but it is easy enough to create another URL that does set this header for testing.

robertknight commented 10 months ago

Chrome are currently exploring a solution for this via Cross-Origin-Opener-Policy: restrict-properties. See https://developer.chrome.com/blog/coop-restrict-properties/.

The next step for us here is to explore whether we can leverage restrict-properties in the context of Hypothesis being embedded on a web page which sets Cross-Origin-Opener-Policy: same-origin, or whether the third-party rendering the top-level page needs to make a change on their end.

robertknight commented 3 months ago

In the case of browser extensions, there is a built-in API for completing OAuth login flows - chrome.identity.launchWebAuthFlow we might be able to use that instead of opening the popup ourselves. If that works, then we've solved this problem in the following cases:

This leaves the bookmarklet and sites that embed Hypothesis. For the bookmarklet, a possible solution would be to have the sidebar iframe request storage access via document.requestStorageAccess, which will give it access to the localStorage and cookies of hypothes.is in a top-level site. The auth popup should then be able to communicate with the sidebar iframe via storage events, cookies or BroadcastChannel.

robertknight commented 3 months ago

I've been looking closer into using alternative communication channels (eg. BroadcastChannel) to pass the cookie back. What I have found is:

Putting these together, a possible workflow for using document.cookie to pass back the auth token would be:

  1. Client attempts to open the popup window as normal. If the window.open call returns null then Cross-Origin-Opener-Policy: same-origin is in effect, and the client won't be able to communicate directly with the popup.
  2. The client could at this point invoke document.requestStorageAccess (I think, I need to check whether step (1) "consumes" the user gesture) so that it can communicate with the popup via cookies instead
  3. After authorization completes in the popup, the popup must redirect to a domain that matches the sidebar, except for the case where the sidebar domain is the same as the authorization domain
  4. The popup window can then use a Set-Cookie header or document.cookie to pass an auth code back to the sidebar
  5. The sidebar can then read the auth code from document.cookie (via polling) or cookieStore (via events)

In order to make this work, every OAuth client that is not hosted on the h server itself will need to register a functional redirect URL, not merely a "placeholder" URL that provides an origin for use with window.postMessage calls.

For OAuth clients which are hosted on the same site as h, once we have storage access we could skip the whole popup flow if the user is already logged into the h website.

robertknight commented 1 month ago

Some new web standards proposals that are relevant: