javascriptiframecross-domainpostmessage

postMessage() cross-origin iframe javascript


I am trying to communicate with cross-origin resources using postMessage(), but am unable to get it to work properly. I have reviewed dozens of other similar questions, but am still stuck.

(https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage, https://javascript.info/cross-window-communication, How can I do cross-domain postMessage?, etc.)

I have this working on the same origin, but when I move to a different origin, I get a standard corss-origin error.

Uncaught DOMException: Blocked a frame with origin ... from accessing a cross-origin frame.

I call postMessage() twice. Once on page load using the wildcard - which is working. And once using a delcared origin - which is what I need, but can't get to work.

Parent:

<html>
    <body>
        <button class="site-btn" onclick="initChatIfr();">Load Iframe</button>
        <iframe lws-ifr="demo-ifr" lws-ifr-status='inactive'></iframe>
    <script>
        const ifrEle = document.querySelector('[lws-ifr="chat-ifr"]');
        function initChatIfr() {
            ifrEle.src = 'https://some-cdn.cloudfront.net/post-message/index.html';
            ifrEle.setAttribute('lws-ifr-status', 'active');
        }

        function listener(e) {
            const acceptedOrigins = ['https://some-cdn.cloudfront.net', 'https://some-sandbox.s3.amazonaws.com', 'http://localhost:3000'];
            
            if (e.data.indexOf('close') < 0 || acceptedOrigins.indexOf(e.origin) < 0) {
                console.log('   not a close event or an unapproved ifr origin')
                return;
            } else {
                console.log(' approved ifr origin')
                ifrEle.setAttribute('lws-ifr-status', 'inactive');
                setTimeout(function() {
                    ifrEle.src = '';
                },300);                    
            }
        }

        if (window.addEventListener){
            addEventListener("message", listener, false);
        } else {
            attachEvent("onmessage", listener);
        }
    </script>
    </body>
</html>

Child:

<!DOCTYPE html>
<html>
<body>
    <button class="site-btn cancel-btn" onclick="closeParentIframe();">Close Iframe</button>
    <script>
        const acceptedHosts = ['some-cf-cdn.cloudfront.net', 'some-sandbox-bucket.s3.amazonaws.com', 'localhost:3000'];
        function closeParentIframe() {
            const pOrigin = parent.origin;
            const pHost = parent.location.host;
            if (acceptedHosts.indexOf(pHost) > -1) {
                console.log(' APPROVED parent origin', pOrigin);
                parent.postMessage('close', pOrigin);
            } else {
                console.log(' UNAPPROVED parent origin', pOrigin);
            }
        }
        console.log('ifr loaded');
        parent.postMessage("hi from the ifr " + location.href, '*');
     </script>
</body>
</html>

Any help will be greatly appreciated!


Solution

  • Looks like the problem is in your child script, you are trying to access parent.origin as well as parent.location.host which is causing the error because it's not allowed given the cross-origin concerns.

    If you want to get the domain of the parent from the child code, you can instead use document.location.ancestorOrigins[0. However, it seems like this is currently not supported on any version Firefox (updated 2022). See https://caniuse.com/?search=ancestorOrigin

    Other than that, the rest looks okay.

    Here's a thread about options how to get the parent's domain from the iFrame: https://stackoverflow.com/a/52399194/14264568