React-stripe-elements: Support <Elements> within a shadow DOM

Created on 23 Jun 2018  ·  24Comments  ·  Source: stripe/react-stripe-elements

This issue was originally reporting a bug when using custom fonts within a shadow DOM. I've changed the title to more accurately reflect the root cause - which is no shadow DOM support whatsoever.


Minimal reproduction (use Chrome)

Environment

Summary

When using stripe elements within a shadow root, passing external fonts to the <Elements> component will result in the iframe form getting an inline visibility: hidden style.

chrome_2018-06-23_14-24-42

Even if this style is overriden, creating tokens will later throw the following error:

We could not retrieve data from the specified Element.
Please make sure the Element you are attempting to use is still mounted.

Other information

The repro above may take a few moments to load. Notice that the fourth box will stay empty.
I'm aware that the fonts are not actually applied to the inner <CardElement> in the repro above.

Most helpful comment

@bcanseco Thanks for that, that's more reason for stripe to support the full DOM and HTML spec, including shadow DOM. Anyone building a payment app that uses shadow DOM would LOVE to not have to fill out those forms, but unless Stripe decides to support the full DOM spec, we have to use weird workarounds like walking the whole tree.

All 24 comments

@bcanseco Hi! Thanks for writing in. I'm Matt and I work at Stripe. You are totally right here--Elements is currently not compatible with shadow DOM. We have this on our backlog and are looking forward to addressing it in the future (no concrete timeline at the moment unfortunately). I will be sure to let you know when the fix does roll out!

Not just Chrome, shadow dom is supported in a number of browsers. For example, firefox support is about to land.

https://caniuse.com/#feat=shadowdomv1

FYI, the way we solve this in stripe-elements custom element is to walk the dom tree until we find the document.

+1 we also really need this for our solution. As web pages become more plugin focused this is going to be important.

+1

For anyone considering workarounds like tokenizing cards using your own CC forms on the frontend and then capturing charges from your backend:

Keep in mind that you eventually need to validate PCI compliance. Yes, your backend never sees credit card information with this approach. HOWEVER, since you're not using the Elements library - you will have to fill in 40 pages of PCI forms every year.

image

More info on that here.

@bcanseco Thanks for that, that's more reason for stripe to support the full DOM and HTML spec, including shadow DOM. Anyone building a payment app that uses shadow DOM would LOVE to not have to fill out those forms, but unless Stripe decides to support the full DOM spec, we have to use weird workarounds like walking the whole tree.

@matt-stripe Are there any updates on the progress for shadow dom support?

Would love to know if this has been fixed - also why is it closed if it's not?

It's worth mentioning that the workaround we use in <stripe-elements> breaks the tab order, as well.

@bennypowers - does it also break the PCI compliance side of things or is that ok?

Also does it do the new PaymentIntent stuff?

[UPDATE]
https://stripe.com/docs/payments/payment-intents/quickstart
Ok so I can see that it doesn't... I will see if I can add this to your component but I am concerned that the PCI element is going to be a problem - could you clarify that for me please.

I wish Stripe would sort it out and do this properly within stripe!

Won't break PCI, since even with my component, your users are still entering their numbers into an iframe to stripe.com.

but without proper tab order, it is inaccessible. You'll probably need some vile tabindex hacking, either that or very careful dom placement to get it to work the way you'd expect.

But i'd be happily proven wrong. all of ☝️ is based on my experience using <stripe-elements> inside an animated multi-step form.

<shameless-plug> I'd love to review a PR to add payment request and other stripe.js features to the element. Please see https://github.com/bennypowers/stripe-elements/issues/13 </shameless-plug>

Hi folks, we don't have any updates regarding shadow DOM support yet. It should still be possible to use Elements without the shadow DOM, so hopefully you're not blocked from integrating Stripe altogether (or forced to fall back to higher levels of PCI).

We'll post here if/when we have updates on shadow DOM support.

There are 8 (pretty minor, fairly repetitive) things in stripe.js which I know of that prevent it from working. There may be more which just happen not to occur in the features we’re using, but still, it’s not a super big diff. I have a patched version which does work, but it will be awkward trying to maintain keeping it in sync with the stripe-hosted script. Any news here?

Shadow DOM causes some issues with the iframes that Stripe.js uses internally for PCI compliance. It may be possible to change Stripe.js to be compatible with Shadow DOM while preserving PCI compliance, but it's definitely more involved than 8 minor patches.

In the meantime, the only workaround is to keep all elements in the main document, doing something like @bennypowers's clever trick in stripe-elements.

There’s nothing which could be considered PCI-related that I’ve encountered. The iframes are still secure (nothing can change that, really, they are controlled by stripe’s origin exclusively, and none of the Shadow DOM issues concern message passing). It’s only a handful of incorrect assumptions about three things:

  1. how to determine if an element is connected (use isConnected, not document.body.contains);
  2. how to determine if an element is focused (getRootNode().activeElement, not document.activeElement)
  3. how to choose the next/prev sequentially focusable element in the tab order on blur (the most complex thing, but this is a UX level issue)

Shadow DOM does not change how iframes behave.

Shadow DOM does not change how iframes behave.

It does. This is the main blocker: https://github.com/whatwg/html/pull/1625/commits/7b567729ac3cc66b074b0df53138c353ec540bb9
See also https://github.com/w3c/webcomponents/issues/145

That means unless all iframes are in the root document, they cannot discover each other to communicate directly.

Apologies, I framed that wrong. I should have said it does not change their security characteristics.

I’m a bit confused now though because the script creates all of these elements and can hold references to them, and is not obviously failing anywhere.

While I'm not sure what your patches exactly do, you might end up creating the element iframes but they wouldn't be functioning properly. In particular I don't believe you'd be able to create a Token or PaymentMethod from a card element.

Also, a reminder that self hosting Stripe.js (especially a modified versions) is in itself not PCI compliant.

Why is that exactly? I should hope that _no_ script I run in my origin could compromise Stripe’s security — is that not the case?

(To be clear — I believe you of course, just looking to understand what’s going on better. This has all been a pretty frustrating experience so far.)

@bathos I ran into this same problem as well. I put together a working solution where I have an