Cross-Domain Communication in React using postMessage
Recently, I had to make two separate React apps exchange data — despite being hosted on different domains. I needed something simple and secure. One of the simplest ways I found to solve this was by using window.postMessage.
The Problem
- App A → running on
https://app.example.com - App B → running on
https://widget.example.com
The goal: when a user is about to access App B, App A should check if the user is logged in and then send a short-lived code securely to App B. App B can then verify this code with the backend. Since these apps are on different domains, they can’t directly share storage or state.
Why postMessage?
With postMessage, a page can talk to an iframe or a popup it created. It’s the safe, built-in way the browser lets different origins send messages to each other.
The Setup
1. Sending a Message
From App A, I open App B in a popup and send a short-lived code, which App B can send to the backend to validate the user’s session:
// AppA.tsx
const openWidget = () => {
// Open App B in a popup
const widgetWindow = window.open(
'https://widget.example.com',
'widget',
'width=800,height=600'
);
if (widgetWindow) {
// Generate or get a short-lived code from your backend
const shortLivedCode = '...'; // Generate this on your backend
// Send the code securely
widgetWindow.postMessage(
{ type: 'LOGIN_CODE', code: shortLivedCode },
'https://widget.example.com'
);
}
};
// Example button
<button onClick={openWidget}>Proceed To Widget</button>It’s important to explicitly specify the target origin (https://widget.example.com) to keep it secure.
2. Receiving a Message
Over in App B, I set up a listener inside a React component:
// AppB.tsx
import { useEffect } from "react";
export default function AppB() {
useEffect(() => {
const handleMessage = async (event: MessageEvent) => {
// Only accept messages from App A
if (event.origin !== "https://app.example.com") return;
const { type, code } = event.data;
if (type === 'LOGIN_CODE') {
// Verify the code with your backend
const isValid = await verifyCode(code);
if (isValid) {
console.log('User is authenticated, proceeding...');
} else {
console.log('Invalid or expired code, redirecting to login.');
window.location.href = '/login';
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
return <div>Waiting for login verification...</div>;
}App B receives the data from App A and exchanges the code with the backend to validate the user session and decide whether to proceed or redirect the user.
Bonus: Sending Back a Response
Sometimes App B might need to respond to App A (for example, to confirm the redirect was handled):
event.source?.postMessage(
{ type: "LOGIN_PROCESSED", result: "success" },
event.origin
);Common Pitfalls
- Forgetting to check
event.origin→ Critical for security. Don’t process messages from unknown domains. - Using
*as the target origin → Okay for testing, but never in production. Always whitelist the exact domain. - Message shape mismatch → Both apps should agree on the format (e.g.,
{ type, payload }).
Additional Security Measures
- Make sure to validate the
codebefore sending or using, on both sides, to ensure it matches the expected type or pattern.
2. Set a Timeout for Window Reference
Get Neira Durmic’s stories in your inbox
Join Medium for free to get updates from this writer.
If the popup fails or is closed immediately — alert the user to prevent confusion and improve UX and security.
setTimeout(() => {
if (!widgetWindow || widgetWindow.closed) {
alert("Failed to open login widget. Please try again.");
}
}, 3000);3. Make sure your backend:
- Issues codes that expire in minutes.
- Accepts codes only once.
- Logs or blocks suspicious reuse attempts.
Final Thoughts
Using postMessage is simple, reliable, and works across domains without violating security rules.
If you’re ever in a situation where two React apps (or even one React app and a vanilla JS widget) need to communicate across domains, don’t overcomplicate it. Start with postMessage.
It’s a valuable approach that every frontend developer should be familiar with.









