Bug 202640

Summary: Tracking blocking breaks remembering login on editor.construct.net
Product: WebKit Reporter: Ashley Gullen <ashley>
Component: DOMAssignee: Nobody <webkit-unassigned>
Status: RESOLVED WONTFIX    
Severity: Normal CC: beidson, bfulgham, cdumez, sihui_liu, webkit-bug-importer, wilander
Priority: P2 Keywords: InRadar
Version: Other   
Hardware: Unspecified   
OS: Unspecified   

Description Ashley Gullen 2019-10-07 09:59:24 PDT
In Safari, the "Prevent cross-site tracking" setting breaks our site from remembering the user's login state. In our case I believe Safari ought to have reasonable heuristics to allow the case.

Steps to reproduce:

1. Register an account on construct.net (it's free)
2. Visit editor.construct.net
3. Choose Menu - Account - Log in
4. Enter login details, tick "Keep me logged in", and click "Log in"
5. Once logged in, reload the page

Expected result: login remembered
Observed result: login forgotten and reverts to guest state; user has to log in again

Turning off "Prevent cross-site tracking" in preferences fixes it and allows it to work again.

As a security defense-in-depth, our login form is on a different origin to the web app. The app is on editor.construct.net and the login form is on account.construct.net. Therefore the user is interacting with a cross-origin iframe, typing in their password, and submitting this to that origin. However Safari still blocks the attempt for this origin to remember the login state.

I completely understand the desire to block the ability for third-party ads to track users and similar user-hostile cases, but in this case it seems Safari has gone too far. Surely typing in your login details to an iframe ought to be considered sufficient interaction to allow storage? It works in Firefox 69, as well as Chrome.
Comment 1 Radar WebKit Bug Importer 2019-10-07 14:05:48 PDT
<rdar://problem/56049872>
Comment 2 John Wilander 2019-10-07 15:12:01 PDT
I loaded editor.construct.net. It only uses resources from *.construct.net and clicking on Login results in cookies set for both editor.construct.net and account.construct.net that persist for months. The cookies are set for .construct.net which means they span the whole website.

I can't do a full login to test what happens from that point since I don't have an account.

Can you you please test yourselves and answer these questions:
1. Which domains are involved in the login? Specifically if any non-construct.net domain is involved.
2. Which kinds storage are involved in the login? Cookies? LocalStorage? IndexedDB?
3. Do you see any state change as a result of the login? Or is nothing accepted?

Thanks!
Comment 3 Ashley Gullen 2019-10-08 03:08:27 PDT
I pointed out signing up an account is free and pretty quick so you should be able to straightforwardly test this yourself. To answer your questions:

1. Only editor.construct.net (top frame) and account.construct.net (login frame)
2. The login state is remebered using IndexedDB.
3. I'm not sure what state you're referring to here exactly. As far as I can tell nothing is allowed to be written to IndexedDB.
Comment 4 John Wilander 2019-10-08 12:11:00 PDT
OK, so you're using IndexedDB. AFAIK, WebKit blocks IndexedDB in cross-origin contexts.

In Web Inspector's console, execute this in your "login" iframe (you can pick frame in the lower right corner of the Inspector):

1. document.location.origin
Output: "https://account.construct.net"

2. window.indexedDB
Output: IDBFactory {open: function, deleteDatabase: function, cmp: function}

3. window.indexedDB.open("TestDatabase", 3);
Output: SecurityError: IDBFactory.open() called in an invalid security context

My advise is to catch errors in your IndexedDB code and see if you are being blocked.
Comment 5 Ashley Gullen 2019-10-09 08:35:19 PDT
I already knew that was happening when I filed this issue. The point is this worked in Safari 12 and now Safari 13 is falsely blocking a legitimate use case, where the user is entering their login details in a frame, so surely are signalling their interest in that origin. Safari 13 should allow this case and not block it.
Comment 6 Ashley Gullen 2019-10-09 08:36:11 PDT
Also, we already identify that we're being blocked, but what can we do about it? It's not clear there's any workaround.
Comment 7 John Wilander 2019-10-09 11:03:58 PDT
Cc'ing Brady.

Brady, the reporter claims this is a regression/change from Safari 12. Did anything in the security checks of cross-origin use of IDB change lately?
Comment 8 Brady Eidson 2019-10-09 11:53:14 PDT
(In reply to John Wilander from comment #7)
> Cc'ing Brady.
> 
> Brady, the reporter claims this is a regression/change from Safari 12. Did
> anything in the security checks of cross-origin use of IDB change lately?

None that I know of?

Sihui?
Comment 9 Sihui Liu 2019-10-09 12:19:03 PDT
(In reply to Brady Eidson from comment #8)
> (In reply to John Wilander from comment #7)
> > Cc'ing Brady.
> > 
> > Brady, the reporter claims this is a regression/change from Safari 12. Did
> > anything in the security checks of cross-origin use of IDB change lately?
> 
> None that I know of?
> 
> Sihui?

We use the same logic to check if we should throw SecurityError.

Maybe some condition value has changed due to other security changes. Need to take a deeper look.
Comment 10 Sihui Liu 2019-10-09 16:14:39 PDT
(In reply to Ashley Gullen from comment #5)
> I already knew that was happening when I filed this issue. The point is this
> worked in Safari 12 and now Safari 13 is falsely blocking a legitimate use
> case, where the user is entering their login details in a frame, so surely
> are signalling their interest in that origin. Safari 13 should allow this
> case and not block it.

Which version of Safari does this work? I tested on Safari 12.0(14606.1.36.9) and I saw the same error.
Comment 11 Ashley Gullen 2019-10-10 03:40:55 PDT
Urgh, I just tried Safari 12 and it's the same there - so I guess this part of our app has been broken for a while now and we only just noticed.

Is there a workaround though? How can we make sure an iframe the user enters their login details to is allowed to save to IndexedDB?
Comment 12 John Wilander 2019-10-10 07:05:32 PDT
(In reply to Ashley Gullen from comment #11)
> Urgh, I just tried Safari 12 and it's the same there - so I guess this part
> of our app has been broken for a while now and we only just noticed.
> 
> Is there a workaround though? How can we make sure an iframe the user enters
> their login details to is allowed to save to IndexedDB?

As mentioned, Safari blocks IDB for cross-origin frames. As far as I know, that’s always been the case. Only the first party domain can use IDB. If you want one of your domains to use IDB, that domain needs to be first party, i.e. be top frame.
Comment 13 John Wilander 2019-10-10 07:07:56 PDT
Resolving as Won’t Fix since this behavior is by design and not a regression in Safari 13.
Comment 14 Ashley Gullen 2019-10-10 07:31:20 PDT
So is it the case that Safari always unconditionally blocks IndexedDB access in cross-origin iframes with no workaround? Even if the user interacts with them?
Comment 15 John Wilander 2019-10-10 07:41:10 PDT
(In reply to Ashley Gullen from comment #14)
> So is it the case that Safari always unconditionally blocks IndexedDB access
> in cross-origin iframes with no workaround? Even if the user interacts with
> them?

That is my understanding, yes. Brady, who is cc’d, knows for sure.
Comment 16 Ashley Gullen 2019-10-15 04:19:08 PDT
We've tried implementing document.requestStorageAccess() in a user gesture (when clicking the login button) and we can see the request is granted. However after reloading the page, IndexedDB is still blocked, so the login is still forgotten despite the user's preference.

If there is no workaround, we are incentivized to tell users to turn off the "Prevent cross-site tracking" setting for Safari to remember their logins. This is a global switch that affects all their browsing. I doubt this is the outcome Apple want.
Comment 17 John Wilander 2019-10-15 08:05:50 PDT
The Storage Access API, as implemented in WebKit, only opens up cookie access. Since your iframe is same-site, i.e. has the same registrable domain as the top frame, it does not need to call the Storage Access API. The iframe has cookie access from the get go.

IndexedDB is blocked in cross-origin contexts. You need to use another mean of persistence for your login credentials if you persist the login in a cross-origin iframe.

This blocking behavior is not new and your login mechanism has likely never worked in Safari. That’s probably due to lack of testing which is unfortunate. Telling users to turn off all privacy protections in their browser is not the right course of action.
Comment 18 Ashley Gullen 2019-10-15 08:45:24 PDT
Why doesn't the storage access API provide access to IndexedDB? Couldn't that be supported?

The purpose of using a cross-origin frame is to hide the storage from the main origin, where we have to run third-party scripts that shouldn't have access to the storage. I don't know what other options we have to persist a login token on the client, other than to weaken our security by allowing third-party scripts access to login tokens. So long as we can't identify a way it's possible to solve this, telling our users to disable "Prevent cross-site tracking" appears to be the only option we have.
Comment 19 John Wilander 2019-10-15 08:57:17 PDT
(In reply to Ashley Gullen from comment #18)
> Why doesn't the storage access API provide access to IndexedDB? Couldn't
> that be supported?

You’re welcome to file it as an enhancement request. The reason why is that IDB has always been blocked this way in WebKit. Cookies, however, used to be accessible cross-site but are no longer when ITP blocks them. Therefore we invented the Storage Access API to support existing use cases.

> The purpose of using a cross-origin frame is to hide the storage from the
> main origin, where we have to run third-party scripts that shouldn't have
> access to the storage. I don't know what other options we have to persist a
> login token on the client, other than to weaken our security by allowing
> third-party scripts access to login tokens. So long as we can't identify a
> way it's possible to solve this, telling our users to disable "Prevent
> cross-site tracking" appears to be the only option we have.

The standard/common way of achieving this is through Secure, HttpOnly cookies. They are not exposed to JavaScript and are also not transferred into the web content process which means they are protected against speculative execution attacks.
Comment 20 John Wilander 2019-10-15 08:58:30 PDT
You can also scope cookies to the host, i.e. including the subdomain, which ensures only requests to that host see the cookie.