// app.example: receives the login result
window.addEventListener("message", (event) => {
const { token, redirect } = event.data;
localStorage.setItem("token", token);
location.href = redirect;
});
// popup on auth.example: sends the result back
window.opener.postMessage({ token, redirect: "/home" }, "*");
Neither side checks who is on the other end.
The receiver doesn't check event.origin. Any window that can reach yours can send a message. That includes a page that opened your app, or any frame inside it. An attacker can:
- send their own token, which logs the victim into the attacker's account
- send
redirect: "javascript:...", which runs script in your origin (XSS)
Fix the receiver:
- Check the origin exactly:
if (event.origin !== "https://auth.example") return; - Check that
event.sourceis the popup you opened. - Check the shape of
event.data. - Allow only relative redirect paths that you validate.
The sender uses "*" as the target origin. "*" means "deliver to whatever page is there now". If the opener window has moved to an attacker's site, the attacker receives the token. Fix: pass the exact origin, postMessage(data, "https://app.example"). The browser then drops the message if a different origin is in that window.
In short: check the origin on receive, and name the origin on send.
- The receiver must compare
event.originexactly with the expected origin. - Check
event.sourceand the shape of the data before using it. - Never navigate to a URL from a message without validating it.
- The sender must pass the exact target origin, never
"*", when the data is sensitive.
Read more