Cross-Origin iframe communication with Window.postMessage

using a browser's Web API to securely message between a page and an iframe embedded within it

ยท

4 min read

Cross-Origin iframe communication with Window.postMessage

๐Ÿค” Why do we need cross-origin iframe communication?

Imagine that you need to integrate with the "3rd party service" that would be used as part of your application.

Both of your companies are just start-ups, and we don't have a complete variety of tools that will make our lives easier, so we choose iframe as the first option. We must integrate what we have now for the beta version. After that, we will refactor the code and will use edge technologies, as our manager promised(๐Ÿ˜‰)

Their app(as an example) could show private information, possibly, some real-time bank details / shipping / trading details, and only available after user authorization.

๐Ÿค“ What could be a better solution?

The best version of integration(IMHO) would be to get a react library with components, hooks, utils, etc., that will do everything for us. For example, check out React Stripe.js Components. Second best - take an Open API(example Stripe API) and implement our own components.

๐Ÿคจ What are we going to build?

๐Ÿ’ญ Idea summary

As the Parent app, we want to login within the iframe with some token, so the iframe could show relative information. Every N mins(5 secs in this case), our token would expire, and the iframe needs to request another. As a bonus, we can change a theme from dark to light, which could happen from both sides.


Mostly I would list code that is only related to the iframe and Web API part and won't focus on things like an app creation or an explanation of "how to deploy to Vercel".

The Parent and the Child apps would be our actual implementation of what we need. For the front-end, we are going to use Next.js and Chakra-UI for components. We would deploy apps on Vercel and Netlify(to be truly cross-origin).

Also, I would use Nrwl Nx's workspaces to have monorepo, keeping run/build processes seamless.

๐Ÿ‘จโ€๐Ÿ’ป Code(skip to this if you don't want to read the Intro)

๐Ÿค– "The Communicator."

๐Ÿ”— iframe-communicator.vercel.app

๐Ÿ”— Github: github.com/andriishupta/iframe-communicator

It is a "special" app you could use for real-world testing to see how messaging works in your app.

iframe-communicator.png

๐Ÿง‘ Parent code

๐Ÿ”— link to the deployed app

๐Ÿ”— source code is available for copying here

As for the Parent app, we will surely have iframe rendered on our side. Let's start from it:

iframe3.png

  • iframeRef is our React.js reference to the DOM element, so we can later use it
  • onLoad - this would send my initial token

Next: how we send the message is Window.postMessage

The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

post-message.png

const postMessage = (message: Message) => {
    iframeRef.current.contentWindow.postMessage(message, CHILD_APP_URL); // OR use '*' to handle all origins
};

postMessage takes a message: Message as the argument - it is our own message kind that we selected and agreed with the Child app to pass through:

The data is serialized using the structured clone algorithm. This means you can pass a broad variety of data objects safely to the destination window without having to serialize them yourself.


To send actual message we are using iframeRef.current.contentWindow as our targetWindow(from documentation) and the function's second parameter is targetOrigin:

Specifies what the origin of targetWindow must be for the event to be dispatched, either as the literal string "*"

I know my targetOrigin, so I am passing it and suggesting you not neglect security risks.


Last but not least, we want to listen to messages from the Child!

listener.png

Security and filtering: we accept only our messages that we are sure in

// skip other messages for security reasons and avoid extensions alerts in console
if (event.origin !== CHILD_APP_URL) {
  return;
}

Now, let's get the data from the MessageEvent and do some checks and act by business logic:

if (message?.type === 'token-expired-from-child') {
  ...
} else if (message?.type === 'theme-from-child') {
  ...
} else {
  //  in case of some random message
}

*for more options this code could be improved with switch/case(who likes), ternary operator, or object literals.

Finish up by adding a listener and return a callback for removal, so when a component goes down, you navigate to another page, where you don't need to listen for an iframe.

window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);

๐Ÿ‘ถ Child code

๐Ÿ”— link to the deployed app

๐Ÿ”— source code is available for copying here

The approach is the same for the Child app, with a twist of where to call postMessage - window.parent.

child-post-message.png


And listening to the messages differs in type.

child-listener.png

๐ŸŽจ Parent app: cross-origin-iframe-communication-with-next..

๐Ÿ‘จโ€๐Ÿ’ป Github: github.com/andriishupta/cross-origin-iframe..


๐Ÿค– "The Communicator": iframe-communicator.vercel.app

๐Ÿ‘จโ€๐Ÿ’ป Github for "The Communicator": github.com/andriishupta/cross-origin-iframe..

๐Ÿ“ Summary

Cross-Origin iframe communication could come in quite handy in specific situations, and we totally could take advantage of two-way messaging to make that even more dynamic. Check for yourself by clicking the examples.

Thanks for reading!

ย