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

Security consideration: user consent before payment #365

Merged
merged 3 commits into from
Apr 27, 2020

Conversation

ianbjacobs
Copy link
Contributor

@ianbjacobs ianbjacobs commented Apr 15, 2020

in line with:
https://github.com/w3c/payment-handler/wiki/2020-Mar-proposed-changes#mandatory-user-interaction-with-payment-handler-window

Implementation commitment:

  • Safari (link to issue)
  • Chrome (link to issue)
  • Firefox (link to issue)
  • Edge (public signal)

Preview | Diff

index.html Outdated
window and the user has an opportunity to confirm a transaction via a
button. But if the payment handler does not open a window, or opens a
window without an opportunity for user interaction, the browser might
prompt the user to confirm the payment handler's behavior before
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or opens a window without an opportunity for user interaction,

How would the payment sheet/browser know if the payment handler didn't provide a button?

I think we need to force a "payment confirmed" event somehow, which must be triggered explicitly by user activation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I look forward to hearing from @romandev, @rsolomakhin, and @danyao on that idea. If we adopt something like that, then this text would become something like "The reason we have that event is to prevent the scenario where...".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a finalizeResponse() method to PaymentRequestEvent that the payment handler must call with a user activation. I worry about this being brittle for scenarios like this:

  • User interaction happens inside a cross-origin iframe in the PH window, so there's no way to transfer that user activation to the PH origin to trigger PaymentRequestEvent.finalizeResponse(). This can be the case if the PH embeds a 3DS flow from a bank or PSP.
  • Browser minimal UI flow where the browser shows a native UI for a PH without showing any web content from the PH.

The alternative I had in mind was for the browser to track user interactions within the payment handler's web content as an internal state, e.g. [[userInteracted]] of the PaymentRequest. I need to dig more into the code to know how hard this would be. The main downside is that we're leaving the definition of "user interaction" to the browser instead of leveraging the standardized "user activation" definition, so be a source of interop subtleties. The upside is that "user activation" as spec'ed today has very complex rules for how they can be transferred. Opening it up to address the iframe case could actually increase the overall security surface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HI @danyao,

The use cases you cited are very helpful.

If it is up to the payment handler to call PaymentRequestEvent.finalizeResponse(),
that seems to leave open the possibility that the payment handler could lie.

I was thinking more along these lines:

  • In a payment handler window, the browser keeps track of all user interaction events, whether at the top level or in descendant iframes.
  • If the number of user events logged before show() == 0 then prompt the user.

I am not a browser maker, so I don't know whether that makes sense.

Regarding the minimal UI use case, that seems like one where the browser could enforce the user interaction requirement itself rather than relying on the content-less PH to do so.

Ian

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is up to the payment handler to call PaymentRequestEvent.finalizeResponse(),
that seems to leave open the possibility that the payment handler could lie.

Payment handler can't lie if the browser rejects the show promise if PaymentRequestEvent.finalizeResponse() is called without a user activation.

Thinking about it a bit more, the iframe use case I described can fit in today's user activation model [1] because click events are allowed to propagate upwards to parent frames, even cross-origin ones. This is good.

For the minimal UI case, we can still ask payment handler to call finalizeResponse(), but browser can override the user activation requirement based on user's interaction with the native UI.

The other question is backward compatibility: how should a browser handle legacy payment handlers that don't yet call finalizeResponse() explicitly? Maybe we can take a two-step approach:

  1. Bridge solution: browser tracks user activation inside PH window and implicitly call finalizeResponse() when the window closes.
  2. End state: PH has to explicitly call finalizeResponse().

We would also need an extra state to PaymentRequest to track if an implementation specific window is ever opened. If not, the show promise should reject when the PH attempts to resolve the promise.

[1] https://mustaqahmed.github.io/user-activation-v2/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be missing something because I haven't looked deeply into this recently.
If I am missing something, please correct me.

I think @danyao 's suggestion is reasonable overall, and I support her opinion.
BTW, This may be a silly question but I'm still missing something. What's the subtle difference between new finalizeResponse() and existing respondWith()?
In my understanding and according to the spec text, respondWith() already checks whether the PaymentRequestEvent.isTrusted is true and doesn't it mean that respondWith() is only allowed by user activation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @romandev - The PaymentRequestEvent.isTrusted check in respondWith() ensures that request.show() is triggered with a user gesture. I think to protect the user from the [one-click collection of detailed user information without UI][1] privacy attack, I think browsers should also require that the user has interacted with the payment handler UI before allowing the showPromise to resolve. Do you think this makes sense?

finalizeResponse() is just a strawman. 😅 I need to study respondWith() a bit more to see if we truly needs a second method. My initial intuition is based on code examples that show a payment handler calling respondWith() immediate in the paymentrequest event handler to return a Promise. If we want to use respondWith() to capture the requirement that the user has interacted with the payment handler's web content, then it probably needs to be called outside of the event handler and as part of a click event handler on the payment handler's web content. I'm not too sure yet how this click event handler can transfer to the service worker...

All that said, I don't think we need to figure this all out before updating the non-normative note here. WDYT @ianbjacobs @marcoscaceres @romandev ? We can tackle the actual design in a separate issue.

[1] https://w3c.github.io/webpayments/proposals/privacy-threat-model.html#one-click-no-ui

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi all,

Let me know if you're ok to merge the security consideration in the meantime.

Thanks!

Ian

index.html Outdated
window and the user has an opportunity to confirm a transaction via a
button. But if the payment handler does not open a window, or opens a
window without an opportunity for user interaction, the browser might
prompt the user to confirm the payment handler's behavior before
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, a user agent might do nothing if a payment handler opens a
window and the user has an opportunity to confirm a transaction via a
button. But if the payment handler does not open a window, or opens a
window without an opportunity for user interaction, the browser might
prompt the user to confirm the payment handler's behavior before

This seems too prescriptive. Would it be sufficient to say something like this?

"...At the same time, user agents should take necessary steps to make sure the user is made aware when a payment request is invoked, and has an opportunity to interact with the payment handler before the merchant is allowed to receive the payment response from the payment handler."

Copy link
Contributor

@danyao danyao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ianbjacobs ianbjacobs merged commit e337bc9 into gh-pages Apr 27, 2020
@ianbjacobs ianbjacobs deleted the sec_20200415-1 branch April 27, 2020 15:20
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

Successfully merging this pull request may close these issues.

None yet

4 participants