Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to know who to prompt for storage access in the first place #8

Closed
kushal opened this issue Feb 5, 2020 · 15 comments
Closed

Way to know who to prompt for storage access in the first place #8

kushal opened this issue Feb 5, 2020 · 15 comments
Assignees

Comments

@kushal
Copy link

kushal commented Feb 5, 2020

(carrying this over from discussion started here and ended here)

Although this proposal aims to solve problems like "Subscribed video service," in practice it's unlikely that a service would want to prompt every single user for storage access when they click the play button just to find the fraction of users paying for ad-free access or hoping to include the video in their browsing history.

Similarly, part of the solution for "Social commenting widgets" not appearing be constantly logging you out (as discussed here) is to at least signal the user is one tap away from recustomizing the widget or writing a comment on a given site without distracting or making false promises to anybody else.

And for federated login / alternative billing services such as ours, we'd like for partners to be able to prominently remind our logged-in users to trigger storage access on their site without distracting visitors who do not have (already-logged-in) accounts.

For all of these use cases, it seems like we need some notion of isLoggedIn or trust tokens or explicit user permission to make storage access a viable, user-friendly solution. It sounded like this would end up being a separate proposal from Storage Access itself but at some level feels critical to cementing the plan for storage access.

@jameshartig
Copy link

jameshartig commented Mar 13, 2020

I agree with @kushal. We also operate a subscription service and would like to know before requesting access that the user might have cookies.

Alternatively, once we request storage access and it exists for some period of time can we keep renewing the access as long as the user keeps visiting? That way we can check on load of storage access was granted and then we'd check if they're logged in and if they are, after user interaction we can renew the storage access. If the storage access wasn't granted then we assume logged out.

Actually it looks like that's how it works in Safari. @johnwilander said

Successful use of the Storage Access API resets that counter.

Would Firefox be open to doing the same?

@laughinghan
Copy link

For reference, the WebKit folks wrote up a strawman proposal: IsLoggedIn

via #2 (comment)

@laughinghan
Copy link

laughinghan commented May 13, 2020

I think IsLoggedIn is a good idea overall, but one very minor concern I have is that it's a little easier to use it in a way that could be abused, than it is to use it in a defensive way.

Specifically, it's easy to write the 3P iframe script to check if isLoggedIn() and communicate it to the 1P without anyone validating that this 1P is someone the 3P wants to communicate with. And if a lot of sites have 3P iframe scripts lacking validation, they could be collected into a fingerprint. This validation is potentially something that the 3P iframe script has to go out of its way to do, which is bad because ideally, the path of least resistance ought to also be the most likely secure one.

It could turn out that this isn't an issue in practice—maybe 3P iframe scripts will usually require an API token from the 1P; or maybe 3P iframe scripts usually won't communicate logged-in status to the 1P in the first place, they just show or don't show a Log In button, and the only indication of whether login was one-click or required a popup is the delay from mouseenter over the iframe to successful login being reported to the 1P (which, due to user interaction requirement, couldn't be realistically collected into a fingerprint).

I don't have any particularly clever ideas for how to address my concern. The obvious idea that occurs to me is to add a required argument to navigator.setLoggedIn(), domains, which would be a space-delimited list of domains, or '*dangerous-allow-any*'. Empty string '' or undefined would be an error. Documentation would advise that if you use '*dangerous-allow-any*', you need to do your own validation of the 1P like checking an API token or ensuring that information from navigator.isLoggedIn() doesn't leak out of the iframe, lest you be unwittingly roped into a tracker's fingerprint (you could even threaten to add such unsafe 3P iframe scripts to a browser block list, to include an element of self-interest).

(I initially thought of just '*' to allow any domain, but I think having the word dangerous in the keyword is important because it's likely to be used in tutorials for expediency, and while '*' can be glossed over, '*dangerous-allow-any*' will demand explanation.)

If there are better ideas, I'd love to hear them?

@othermaciej
Copy link

Allowing isLoggedIn() to be called at all from a third-party iframe potentially creates a tracking vector, so it might not be feasible to allow that. (Requires more analysis.)

@laughinghan
Copy link

@othermaciej It would just be 1 bit of fingerprinting info per cooperating 3P (or 3P that fails to validate the containing 1P) on which the user had manually clicked Yes on the permission dialog, right?

@andymatuschak
Copy link

andymatuschak commented Jun 4, 2020

I'll add my own story from the field (ex-Apple here! hi! 👋) . I'm having great difficulty creating a good user experience in the context of Storage Access without something like this API. My use case is congruent to the social commenting widgets.

Once a user's begun an interaction with the 3P, they must authenticate to complete that interaction. It's possible that they've already authenticated, but we can't tell because storage access is restricted. Probably we want to do something like: first requestStorageAccess, then if we discover that the user has not logged in, present a login interface. But it's not clear how to present an affordance for that interaction to the user.

To label a button "Sign in" would be confusing because it may not actually ask the user to sign in! Instead it might present the requestStorageAccess agent permission UI, or if we can be granted access without permission and the user has previously signed in, it might result in the user being signed in with no further interaction. Still more confusing is the worst case, in which storage access is restricted but the user has not previously signed in: the user might first be prompted with the requestStorageAccess agent permission UI, and then with a login form. Quite disorienting, and quite a lot of interaction for a low-stakes embedded interface!

We could label such a button "Connect to XXX," and then the interaction would be less surprising, but the worst-case double-interaction is still awfully rough.

It would be a much smoother experience if we could requestStorageAccess only if the user isLoggedIn. That would handle the worst-case double interaction, and it would allow us to clearly label the button as either "Connect to XXX" in the case of isLoggedIn but with restricted storage access, or as "Sign in" in the case of !isLoggedIn.

Another strategy (which I suspect you all won't like!) would be an API which would allow the 3P to determine if requestStorageAccess would need to present a user approval UI. With this API, we could silently request access upon first user interaction if they'd previously approved access. This API would not help the first-use scenario, but it would at least handle the returning user happy-path case. (An alternative API design for this idea: requestStorageAccess could consume an option—e.g. silent—which, when set, would cause the request to immediately reject if user approval would be required)

In any case, thank you all for what you're doing to protect users' privacy. There are some UI edge cases to work out here, but those are secondary to this important work.

@laughinghan
Copy link

@andymatuschak In case you haven't seen it, I think @johnwilander's comment on a precursor ticket applies to your case:

I think this comes down to old world view vs new world view. The "Show the Sign In button when we have zero cookies" UI was most probably built under the assumption that if the user is logged in, the third-party iframe will know because third-parties have access to cookies. If the assumption instead is that third-parties don't have access to cookies, one would have to build the iframe's UI accordingly, i.e. "We won't know whether or not the user is logged in until they interact with our iframe."

Also, I'm not sure about this:

It would be a much smoother experience if we could requestStorageAccess only if the user isLoggedIn. That would handle the worst-case double interaction

When they click "Sign in", wouldn't you still have to requestStorageAccess, so there's a double-interaction regardless? If you don't, you won't have any way to remember that they're signed-in when they visit a different site that embeds your widget.

@andymatuschak
Copy link

andymatuschak commented Jun 4, 2020

@andymatuschak In case you haven't seen it, I think @johnwilander's comment on a precursor ticket applies to your case:

Yep, it does! Thank you for the pointer. In my case, I'm very happy to build the UI around this spec and its restrictions. The problem is that even in this greenfield context, I'm not sure how to build a good experience! But it's good to see @johnwilander open to isLoggedIn being available in this situation.

When they click "Sign in", wouldn't you still have to requestStorageAccess, so there's a double-interaction regardless? If you don't, you won't have any way to remember that they're signed-in when they visit a different site that embeds your widget.

If isLoggedIn is false, I wouldn't requestStorageAccess: I'd open the login UI directly, have it save an HttpOnly Secure cookie on completion, pass some credential back to the opener via postMessage, and use that to make subsequent requests.

@laughinghan
Copy link

But if you don't requestStorageAccess, the HttpOnly Secure cookie will be partitioned, and isLoggedIn() will be false forever, no?

@andymatuschak
Copy link

Ah, so, I present my login UI in a popup window (now first-party, navigated to my origin). The login request is made in that context, so the HttpOnly Secure cookie would be set in unpartitioned cookie storage. Back in the 3P opener, though, I suppose I'm asking that isLoggedIn query unpartitioned cookie storage, even if storage access is still restricted.

@andymatuschak
Copy link

One more thought, which could partially mitigate the privacy concerns: in terms of making a good user experience, it'd be fine if isLoggedIn() could be accessed in the 3P context only after user interaction! That restriction would make it much harder to accumulate a bit vector to fingerprint the user across many different embedded 3Ps.

@laughinghan
Copy link

laughinghan commented Jun 6, 2020

Oh sorry, when you said "open the login UI directly" I thought that meant there would be a login form directly inside the iframe. If you open your login UI in a popup, I think the compatibility measure means that you actually should have cookie access, even without requestStorageAccess.

it'd be fine if isLoggedIn() could be accessed in the 3P context only after user interaction!

To clarify, if this were the case, then you would not be able to "clearly label the button as either "Connect to XXX" in the case of isLoggedIn but with restricted storage access, or as "Sign in" in the case of !isLoggedIn", right?

Instead you would use isLoggedIn to decide, upon clicking on the "Connect to XXX" button, whether to requestStorageAccess or open the login popup?

It seems that the double-interaction of the permission prompt followed by a login popup is intentionally encouraged by design: https://github.com/privacycg/storage-access#the-user-is-not-yet-logged-in-to-the-embedee

I think you could actually avoid this double-interaction, though, by using the compatibility measure: instead of bothering with isLoggedIn or requestStorageAccess, you always open a popup and it either displays a login form, or if you're already logged-in it just has a button "Connect to XXX from YYY" (where YYY is the first-party).

That's probably a bad sign for the design of this API, if the legacy flow is better for developers than the intended use of this API.

@andymatuschak
Copy link

andymatuschak commented Jun 6, 2020

Oh sorry, when you said "open the login UI directly" I thought that meant there would be a login form directly inside the iframe. If you open your login UI in a popup, I think the compatibility measure means that you actually should have cookie access, even without requestStorageAccess.

Right, but I'm going the more baroque server-cookie + postMessage path because I've been duly warned that this compatibility measure is temporary and will go away. :) I wrote plenty of those warnings to developers myself when I worked on UIKit, so I wouldn't want to build new software which ignores one!

To clarify, if this were the case, then you would not be able to "clearly label the button as either "Connect to XXX" in the case of isLoggedIn but with restricted storage access, or as "Sign in" in the case of !isLoggedIn", right?

I'm sort of lucking out here, I guess. In my UI, it wouldn't make sense to present authentication UI until the user had already interacted. So I'd have a chance to silently check isLoggedIn before I need to present the authentication button. This seems like a pretty common configuration for the "social commenting widget" scenario.

Then, if isLoggedIn is true, I can label the button "Connect to XXX." Clicking it performs requestStorageAccess. If isLoggedIn is false, I label it "Sign in," and clicking it opens the login prompt.

It seems that the double-interaction of the permission prompt followed by a login popup is intentionally encouraged by design: https://github.com/privacycg/storage-access#the-user-is-not-yet-logged-in-to-the-embedee

Yikes. You're right, the double interaction is implied by that answer. That's an unacceptable user experience in my book, particularly since it's in the extra-sensitive onboarding path, and I'll certainly go to enormous lengths to avoid it.

I think you could actually avoid this double-interaction, though, by using the compatibility measure: instead of bothering with isLoggedIn or requestStorageAccess, you always open a popup and it either displays a login form, or if you're already logged-in it just has a button "Connect to XXX from YYY" (where YYY is the first-party).

That seems true! But involving a popup in every session—including the common-case happy path—is also unacceptable UX in my mind. Popups are quite disorienting and add a lot of friction. I definitely wouldn't use a popup at all if not for the 1P interaction requirement (I'd embed the login UI inline). I'd really like to create a zero-interruption "happy path" when users are signed in and have authorized storage access.

~

FWIW, the UX impact here is so onerous that I've resigned myself to building a Safari Extension to ensure my users can avoid these frictions. Of course, many won't install it, so I still want to make the extension-less case as smooth as possible.

@jespertheend
Copy link

Re: WICG/first-party-sets#53 (comment)
Tbh I didn't have a specific use case in mind. But I remember using an api a while back that needed permissions. I ended up undoing my changes because the pop up was too intrusive to show to every user.

One use case I can think of: A settings screen that stores users preferences, but if storage requires a pop up, simply store the preferences in memory rather than in persistent storage. I realise that this can currently be achieved using hasStorageAccess() but WICG/first-party-sets#53 (comment) specifically mentioned requiring a call to the Storage Access API, where some browsers automatically grant access. In such a case I think it would be preferable to have way to see in advance whether access will be granted automatically or not.

@hober
Copy link
Member

hober commented Feb 8, 2022

The conditionally-show-some-UI use case (e.g. blogspot.com showing admin UI on the blog of the visiting blogger.com user) is an interesting one, and one where the Storage Access API does impose a bit of a UX ding relative to the previous status quo. That said, I doubt this is something the Storage Access API should address directly (though perhaps #83 is a path forward there); I think the Login Status API or maybe the FedCM work could be the solution we need here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants