stripe / stripe-js

Loading wrapper for Stripe.js
https://stripe.com/docs/js
MIT License
626 stars 154 forks source link

[BUG]: in-iFrame Apple Pay support in Safari Preview >=167 #484

Closed ryan-lambert-IEX closed 1 year ago

ryan-lambert-IEX commented 1 year ago

What happened?

Hi,

Apologies if this is the wrong place for this, but I am trying to test Apple Pay support in an iframe on the latest version of Safari (Apple Pay support in an iframe was added here PR and is currently testable on Safari Technology Preview)

Historically, Apple Pay does not work in an iframe. It was updated to work with the allow=payment attribute in the above PR. I tested this by going to any webpage -> inserting an iframe with a URL which supports apple pay and here were the results:

Apple Pay - interactive demo Works great only on nightly build Stripe Payments Demo Does not work iframe on nightly build

This points to an issue with the current version of Stripe. If anyone could provide an example with Apple Pay working inside an iframe then it would disprove my hypothesis, but so far I have not gotten it to work in any Stripe example vs. it working in non-Stripe examples

Thank you!

Environment

Safari release 177 (Safari 17.0 Webkit 186617.1.4.3)

Reproduction

https://stripe-payments-demo.appspot.com/ inserted into an iframe

brendanm-stripe commented 1 year ago

Hey @ryan-lambert-IEX -- currently Apple Pay is only supported in iframes from the same domain, it's not available when framed from another domain.

Our team is aware of the beta feature and knows that developers are ready to use it! Once Safari 17 is out of beta and this feature is GA, we'll re-evaluate the support of cross-origin iframes for Apple Pay.

ryan-lambert-IEX commented 1 year ago

@brendanm-stripe thank you for speedy response + clarity - would love to see support added when available

ESoch commented 1 year ago

@brendanm-stripe, are there any examples out there of using Apple Pay within an iframe on the same origin as the parent?

This is something I've been trying to do but am getting this error:

Unhandled Promise Rejection: InvalidAccessError: Trying to start an Apple Pay session from an insecure document.

The SSL certs, etc. seem to be fine for my website as well as the domain being properly registered.

ryan-lambert-IEX commented 1 year ago

@brendanm-stripe This is now released per https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes#Apple-Pay

Any updates as to when Stripe will add support?

MichaelWCollier commented 1 year ago

Also interested in support for this feature. Any updates are greatly appreciated.

rno-iex commented 1 year ago

Same here would love this feature. Any ETA to share?

brendanm-stripe commented 1 year ago

Hey folks, some good news to share here: you should now be able to do this by adding allow="payment" on your iframe. Still ironing out the corresponding docs changes, but the support should be available now.

Edit: docs changes have landed here: https://stripe.com/docs/apple-pay?platform=web#web-integration-considerations

Using an iframe with Elements: When using an iframe, its origin must match the top-level origin (except for Safari 17 when specifying allow="payment" attribute).

ryan-lambert-IEX commented 1 year ago

Hey @brendanm-stripe I think this isn't quite working yet. After failing to get our own SDK work I tried embedding your demo https://stripe-payments-demo.appspot.com/ on a third party website with the allow="payment" flag (I used Notion, see image). image

The ApplePay button appears now but, upon clicking it, I immediately receive an error about the payment unable to be completed. The ApplePay demo flow works fine when I use https://stripe-payments-demo.appspot.com/ directly outside an iframe.

This is the exact same behaivor we are seeing locally.

Mind looking into this?

Hlefreyr commented 1 year ago

Same for us. ApplePay button does not work from iFrame on iOS 17.

brendanm-stripe commented 1 year ago

Looking into this - I can see the same behaviour right now that the Apple Pay sheet seems to immediate go to "Payment Not Completed" in my own tests.

HVNT commented 1 year ago

This is not fixed. Can we re-open it?

brendanm-stripe commented 1 year ago

Just a brief update: we've got a fix for this rolling out asap, so you should start to see behaviour change.

Thank to all those who reported this!

ryan-lambert-IEX commented 1 year ago

I just tested, can confirm it works as expected.

HVNT commented 1 year ago

Works for me as well. Thank you!

AdamPaddle commented 1 year ago

Hey, sorry if this should be asked elsewhere. I'm trying to work out what the restrictions are for this. It also works for me if I embed https://stripe-payments-demo.appspot.com/ into Notion but doesn't if I embed it into other sites like Google. The Payment Request window opens shows processing then closes.

I get a 400 error when Stripe JS is calling https://api.stripe.com/v1/apple_pay/sessions and I can see it passes the top level domain through rather than the iframe domain.

CleanShot 2023-10-25 at 09 38 11@2x

Is this expected?

I also get the console message Could not create Apple Pay session. Please make sure you have registered this Stripe account. For more information, see https://stripe.com/docs/apple-pay#web.

Is it necessary to register the top level domain as well as the iframed domain?

justinmichael-stripe commented 12 months ago

@AdamPaddle Yep, the top-level domain needs to be registered.

wesleyyee commented 11 months ago

@justinmichael-stripe Is there any way to just register the domain of the iframe?

brendanm-stripe commented 11 months ago

@wesleyyee No, the top-level domain must also be registered as this is where Apple considers to be the origin of the required user interaction.

wesleyyee commented 11 months ago

gotcha, thanks!

ryan-lambert-IEX commented 10 months ago

@brendanm-stripe

Why is the top-level parent domain passed to the apple-pay-gateway.apple.com/paymentservices/startSession instead of the iframe domain? Could this possibly be changed?

Rationale for this would be that the parent is explicitly enabling the child frame to allow payment via allow="payment" which is the origin of the payment interactions.

Requiring the parent to also be registered negatively impacts the portability of the child frame which is registered.

Would love to see the child domain passed to apple vs the top-level parent

alekseyg commented 10 months ago

Is the Apple Pay button supposed to show up at all when the top level domain isn't registered with Apple?

We have an embeddable donation widget for nonprofits they can put on their site. We had two issues happen tonight:

  1. When the iframe doesn't have allow="payment", the initialization of PaymentRequest hangs in Safari with the following errors in the console
    [Error] Feature policy 'Payment' check failed for iframe with origin 'https://www.pledge.to' and allow attribute ''.
    d (v3:1:578003)
    (anonymous function) (v3:1:522928)
    (anonymous function) (v3:1:567157)
    (anonymous function) (v3:1:152786)
    [Error] Unhandled Promise Rejection: SecurityError: Third-party iframes are not allowed to request payments unless explicitly allowed via Feature-Policy (payment)
    (anonymous function) (v3:1:578003)
  2. When the iframe does have the attribute, the button shows up on domain that are registered with Apple and works there, but also shows up on other domain and when clicked, the payment sheet comes up for a second but then disappears and a request to stripe fails with a 400 response and JSON including the error mentioned by @AdamPaddle
wesleyyee commented 10 months ago

@brendanm-stripe

Why is the top-level parent domain passed to the apple-pay-gateway.apple.com/paymentservices/startSession instead of the iframe domain? Could this possibly be changed?

Rationale for this would be that the parent is explicitly enabling the child frame to allow payment via allow="payment" which is the origin of the payment interactions.

Requiring the parent to also be registered negatively impacts the portability of the child frame which is registered.

Would love to see the child domain passed to apple vs the top-level parent

@brendanm-stripe would this be possible?

LeadDreamer commented 10 months ago

This is an ENORMOUS security risk - if the parent domain is NOT registered, what's to prevent ANY (spam) domain embedding this "portable" iFrame?

ESoch commented 10 months ago

This is an ENORMOUS security risk - if the parent domain is NOT registered, what's to prevent ANY (spam) domain embedding this "portable" iFrame?

@LeadDreamer,

My two cents; you would likely want to register and validate domains embedding your application (which I presume in turn loads Stripe).

LeadDreamer commented 10 months ago

Yeeeesass ... Hence the current system requirement...

wesleyyee commented 10 months ago

to clarify my particular request, this is for a subdomain, and was part of the recent fix that went out with safari 17 https://github.com/WebKit/WebKit/pull/11485

ryan-lambert-IEX commented 10 months ago

@LeadDreamer can you explain why enabling Apple Pay within the child frame via allow="payment" adds an additional "enormous" security risk? Is this somehow different than the existing security risk of inherent to embedded content?

I would argue that using the child domain better matches the intended functionality of the allow="payment" attribute. The restriction to use the top-level domain is something Stripe added, but it sorta defats the entire purpose of this attribute added by Webkit by requiring the parent to be origin.

This problem is way easier to handle in the child via CSP vs. asking the parents to upload apple verification files

LeadDreamer commented 10 months ago

Sure! => You build your "portable" child frame on your domain => Hacker embeds a copy of your frame on their unregistered website => Hacker sends dozens to hundreds of unauthorized charges via said embedded frame.

In your scenario, you're asking Apple to trust that you are vetting sites where the child frame is embedded appropriately. Seems to be asking a bit much.

ryan-lambert-IEX commented 10 months ago

Sure! => You build your "portable" child frame on your domain => Hacker embeds a copy of your frame on their unregistered website => Hacker sends dozens to hundreds of unauthorized charges via said embedded frame.

In your scenario, you're asking Apple to trust that you are vetting sites where the child frame is embedded appropriately. Seems to be asking a bit much.

Thank you. Totally understand the concern around XSS or clickjacking + iframes. I think the risk you described exists to to any site using Stripe today regardless of apple pay's availability to the user. In all cases, it seems better to specify CSP headers in the child which controls where the child can be displayed.

In Stripe today, ApplePay requires the parent to upload a verification file to the root of the the website (and you can only have one file per site without some routing trickery) and verify it with Apple via the Stripe Dashboard / API. It seems overly cumbersome and contrary to the spirit of Webkit's change which explicitly allows ApplePay via the new allow="payment" attribute. In other PSPs, you can simply rely on the allow="payment" attribute without having to add this verification file since the child is sent as the origin to apple-pay-gateway.apple.com/paymentservices/startSession.

I could be missing something, but to me it seems like this is a bug contrary to the spirit of Webkit's new functionality; I understand there are security concerns, but those aren't unique to ApplePay and such concerns are better mitigated by CSP.

alekseyg commented 10 months ago

@LeadDreamer can you explain why enabling Apple Pay within the child frame via allow="payment" adds an additional "enormous" security risk? Is this somehow different than the existing security risk of inherent to embedded content?

I would argue that using the child domain better matches the intended functionality of the allow="payment" attribute. The restriction to use the top-level domain is something Stripe added, but it sorta defats the entire purpose of this attribute added by Webkit by requiring the parent to be origin.

This problem is way easier to handle in the child via CSP vs. asking the parents to upload apple verification files

From my conversations with Stripe support, they claim that this is a limitation Apple put on things rather than Stripe. However, if that is not true and this limitation is indeed created by Stripe, then I would agree with you that Stripe should not be doing this. Google Pay and the vanilla Stripe credit card components work just fine in iframes and it is up to the developers to secure things. The alternative in order to be consistent is to arbitrarily lock down every payment method, and that would be a very unpopular move. That said, it would be nice to get some clarification on where the actual limitation stems from.

Edit: I will say that I did not see any mention of this limitation in Apple's/WebKit's release notes, so I'm not sure whom to believe.

ryan-lambert-IEX commented 10 months ago

@alekseyg this limitation is from Stripe since they are changing the origin of the request to apple-pay-gateway.apple.com/paymentservices/startSession; we have a version of our SDK which interacts with Apple directly and it works fine inside an iframe. But it's nice to use Stripe since we get all the nice Stripe features, so would love it if you changed this @brendanm-stripe

mPaella commented 8 months ago

@brendanm-stripe is there any update to the question above? regarding the limitation being from stripe, not apple, and stripe changing the origin of the request to the parent rather than the child

i understand the security discussion above, but as @ryan-lambert-IEX has said:

In all cases, it seems better to specify CSP headers in the child which controls where the child can be displayed

many of us, i believe, are building applications specifically meant to be embedded in any domain, but on top of that have implemented origin limitations ourselves - meaning the security concerns would not apply

with that said, i think it would be great to have an option at the very least to enable this "less secure" flow, where the child's origin is used, and the child is the only one required to serve the .well-known verification file

brendanm-stripe commented 8 months ago

The requirement is for the top-level domain where Apple Pay will ultimately appear (to a user looking at their browser address bar, say) to be registered.

Applications meant to be embedded on other domains are fine, but you will need to have your users provide you with their domains so that you can register them via the API on their behalf (assuming this is a Connect scenario using stripe-account headers).

If you have a use case where this is not possible for some reason, I would ask you to write in to support with a clear description & example of that so we can evaluate.

alekseyg commented 8 months ago

The requirement is for the top-level domain where Apple Pay will ultimately appear (to a user looking at their browser address bar, say) to be registered.

The issue is that this seems to be a requirement imposed by Stripe rather than Apple, and so far there has been no good explanation as to why Stripe took it upon themselves to do this. I have found no mention of this requirement in Apple's docs and others have experimented with bypassing Stripe's code successfully in this thread. I imagine this could drive some customers who care deeply about this feature to Stripe's competitors.

shirts commented 8 months ago

@alekseyg I think @brendanm-stripe is indicating that the restriction is now removed on Stripe's end and only the top-level domain – that contains the iframe at another domain – needs to be verified.

I came across this thread because in production for over a year I've had an iframe at another domain that uses Apple/Google Pay. Apple Pay has been configured but has never rendered due to different origin. However it just started working on Feb 2.

Reaching out to Stripe support to see if anything changed:

"We recently rolled out the enforcement for top level parent domain registration when Elements that support wallet payment methods are integrated in cross domain iFrame. Apple Pay inside Payment Element, Express Checkout Element, and Payment Request Button will not be rendered if merchants integrate the Elements in their own cross-origin iFrame and don't have their parent domain registered. This change was to better adhere to Apple's requirement (that only the top level needs to be registered)."

brendanm-stripe commented 8 months ago

@mPaella @alekseyg appreciate the feedback here.

If you have functional cross-origin framing examples without top domain registration, can you share that for us to review? Something in test mode where we can complete a payment using test card details would be ideal.

ryan-lambert-IEX commented 8 months ago

After retesting with our own SDK, I believe I was originally incorrect and Apple does indeed seem to require the top-level domain be used when forming a valid session object.

I am able to get a session object, but it fails completeMerchantValidation. When running the debugger locally via $ log stream --level debug --predicate 'subsystem == "com.apple.passkit"' , I see

com.apple.PassKit.PaymentAuthorizationUIExtension: (PassKitCore) [com.apple.passkit:Payment] 
Application failed to provide a valid merchant session. We can't proceed to authorize the transaction.

The session validation works when I pass in the top-level domain. This is all while using the allow="payment" attribute on my iframe which is registered under our merchant domain umbrella.

I would love to (again) be incorrect, so anyone with any working counter-examples would be appreciated.

alekseyg commented 8 months ago

@ryan-lambert-IEX Well, that's quite the bummer. It would be nice to get more data by testing these scenarios:

@brendanm-stripe I've been retesting various scenarios to get more data and I noticed that stripe.js appears to detect whether Apple Pay will work now and no longer renders a button that won't. So that should mean I can remove my hack that disabled Apple Pay based on a whitelist and rely on the config we have in Stripe now, right?

brendanm-stripe commented 8 months ago

@ryan-lambert-IEX thanks for trying that out, that aligns with our understanding of the restrictions.

@alekseyg Yes, that was a fix we put in place after learning of the gap in the early cross-origin support here. With the framed domain registered we would originally allow Apple Pay to show, but it would not work due to the top level registration requirement. As you noticed, this has been corrected to properly not show Apple Pay if the top domain is not registered.

espen commented 7 months ago

I am seeing this even if I am not using iframe on my payment page. I don't seen any iframe reference in stripe.js so not sure how it is injected. Is there some region/card specific trigger for Apple Pay that makes Stripe use an iframe? I am unable to replicate this myself but we have logged many errors of "Third-party iframes are not allowed to request payments unless explicitly allowed via Feature-Policy (payment)" in stripe.js: "https://js.stripe.com/v3:1:607222→ d". However this is only occurring for customers in Latvia.

brendanm-stripe commented 7 months ago

I am seeing this

I'm not sure what "this" refers to here. Very broadly, Stripe.js uses many iframes working in concert to provide functionality.

If you suspect there is a bug in @stripe/stripe-js please open a new issue with details (errors, reproduction steps etc). If you're having an issue with payments on your account, please contact Stripe Support describing your issue: https://support.stripe.com/contact

espen commented 7 months ago

I'm not sure what "this" refers to here. Sorry if I was unclear, 'this' as in the same behaviour as this issue. But I am not using an iframe on my site so I should open a new issue or contact Stripe support.

Very broadly, Stripe.js uses many iframes working in concert to provide functionality. And does Stripe.js add "allow=payment" to these iframes? Because based on what my js error logs says it is not (though I am unable to replicate this error so I am not 100% what is going on).

wesleyyee commented 3 months ago

@brendanm-stripe to be absolutely clear this still holds true for subdomains?

wesleyyee commented 3 months ago

@ryan-lambert-IEX was that iframe served with a subdomain of the top level domain?