FrontendPro

129Implement a secure postMessage communication protocol between a parent page and embedded iframe

hardAbout 75 minutesCodingsecuritypostMessageiframesrpc

Problem

Your page embeds a payment widget in a cross-origin iframe. They need to talk: the parent asks for the widget's height, the widget reports "payment completed". Any other page or frame can also send messages to either window, so every message must be treated as hostile until proven otherwise.

Implement createSecureChannel({ self, target, targetOrigin, allowedOrigin, protocol = "fp-rpc/1", timeout = 3000, createId = () => crypto.randomUUID() }):

  • self: where messages arrive (window in a browser; anything with addEventListener("message", …)).
  • target: the other window (iframe.contentWindow or window.parent); it has postMessage(data, targetOrigin).
  • Returns { connect(), handle(type, handler), request(type, payload), close() }.

Protocol:

  • Handshake: connect() sends { protocol, kind: "hello", session } with a fresh random session id and resolves when the other side answers { kind: "ready", session }. The receiving side adopts the session from a valid hello and replies ready. Until then, it ignores requests.
  • RPC: request(type, payload) sends { protocol, kind: "request", session, id, type, payload } and resolves with the result, or rejects with the handler's error message, or with an error named "TimeoutError" after timeout ms. request before a session exists rejects.
  • handle(type, handler) registers an async handler; the response is { kind: "response", session, id, ok, result | error }. Unknown types answer with an error.

Security rules for every incoming message: ignore it unless event.origin === allowedOrigin, event.source === target, the data is an object with the right protocol, a known kind, the expected fields, and (after the handshake) the current session. Always send with the exact targetOrigin, never "*". Never throw on bad input.

Try it yourself

Write your solution, run it, then submit to check it against the tests.

solution.jsJavaScript

Press Run or ⌘ Enter to execute your code.

Step-by-step

event.origin is set by the browser and cannot be forged, so compare it exactly with the expected origin (never with startsWith or includes: https://pay.example.com.evil.net starts with your origin). event.source tells you which window sent it; checking it rejects other frames from the same origin, such as a second widget instance.

Unlock the full solution

What's inside

  • Step-by-step
  • Approach
  • Solution
  • Complexity
  • Edge cases
  • Follow-up questions
  • Tests

Includes 2 code listings, 5 edge cases, 4 follow-up questions.

One-time payment by UPI, card or net banking. Launch price.

All 150 answersOne payment, yours forever