Skip to content

Cross-Origin Website Integration via Message Passing and Custom Protocols

Integrating different Web applications is often challenged by the browser’s same-origin policy (SOP, c.f. Same-Origin Policy). This policy assures that documents from distinct origins (combination of scheme, host name and port number) are isolated from each other. For example, if the origins of two documents differ, DOM node access or JavaScript method access between the two documents is prohibited. If an interactive integration of cross-origin Websites is to be achieved, ways around the policy have to be found.

Problem Situation

The specific problem situation we are facing can be described more vividly in terms of a concrete example. In the CoreMedia Studio, we create and edit content that is rendered into Web documents. As can be seen in the picture, the editing interface is made up of two parts.

studio-preview

CoreMedia Studio with embedded (iframed) preview application

On the left-hand side is a form panel with a multitude of property editors for the various parts of the edited content (for example title, detail text, linked content etc). On the right-hand side is the preview panel that shows a preview of how the resulting Web document will look like. The preview is not an integral part of the Studio but an independent Web application that is embedded into the Studio via an iframe. Unfortunately, just showing the preview is not sufficient in our setup. Several features of the preview integration require bidirectional interactions between the embedded preview frame and the surrounding Studio application.

In our previous implementation, these interactions were based on direct DOM and JavaScript access between the frames. For this reason and in order to conform with the same-origin policy, the Studio and the preview Web application had to belong to the same origin. The two applications either had to run on the same host and port or a proxy servlet was necessary to proxy the preview application into the Studio’s origin.

While this approach is a convenient way to circumvent the same-origin policy, it yields the security threat of cross-site scripting attacks. With the Studio and the embedded preview belonging to the same origin, not only trustworthy application code has full access to all the frames but also malicious code that might be included in one of the frames. Our main concern here is attacks coming from the preview application, trying to alter content via the session of the surrounding Studio application. The preview application is generally more likely to be vulnerable as a large amount of external content is possibly included in the rendered documents. Consequently, the same-origin policy should not be circumvented as it is an effective mechanism to separate the preview and the Studio application. Of course, in this case other ways have to be found to allow interaction between the preview and the Studio application.

After this real-world introduction our generalized problem statement reads as follows:

We seek an interactive integration of Web applications into a host application (via iframes) where host and guest applications belong to different origins.

There exists quite a body of solution approaches (c.f. Ways to circumvent SOP). Among them, we found the window.postMessage method the most adequate for our purposes. Host and guest application do not interact via direct access but only via message passing. This allows for a very loose coupling between host and guest application. In accordance to agent-/actor-based programming models, both sides keep autonomy over their respective internal state and behaviour.

In the example described above, user interactions on either the Studio or preview side trigger the transmission of messages to the opposite side. Messages are received and processed by the recipient asynchronously and independently. Some of the messages just serve the purpose of informing the opposite side of something. Others are request messages, demanding an answer to continue processing.

Leveraging window.postMessage

The window.postMessage API is part of the HTML5 specification (Web Messaging spec) and it is supported by recent versions of all major browsers. It allows to let windows communicate via messages. In our aforementioned example, the Studio browser window communicates with the preview iframe’s content window.

However, the window.postMessage API is rather low level. It can be a hassle when used directly. Two of the more annoying reasons are as follows:

  • Registering listeners on the window for message events is easy. But if many messages of different types and from different senders are passed between windows, the listeners have to individually filter out, which messages they are actually interested in.
  • A window.postMessage call is a fire-and-forget call. Request-response patterns have to be realized individually.

For this purpose we devised a message service (as a globally accessible JavaScript object) that leverages the window.postMessage API and provides a more high-level messaging layer. First of all, messages result from a JSON-serialization of objects that are expected to conform to the following inner structure:

{
  type: MESSAGE_TYPE,
  [replyWith: CONVERSATION_ID],
  [inReplyTo: CONVERSATION_ID],
  body: {
    [property_1: value_1],
    ...
    [property_n: value_n]
  }
}

Each message carries a specific message type. An optional replyWith header signals that a response is expected in return to this message. An optional inReplyTo header signals that a message is a response to a previously sent request. A message’s body is a simple map of property-value pairs.

Based on this message format, the interface of the message service looks as follows:

function registerMessageListener(messageSource/*Window*/, messageType/*String*/, listener/*Function*/);

function unregisterMessageListener(messageSource/*Window*/, messageType/*String*/, listener/*Function*/);

function sendMessage(messageRecipient:/*Window*/, messageType/*String*/, messageBody/*Object*/, callback/*Function (optional)*/);

function sendResponse(messageRecipient/*Window*/, inReplyTo/*String*/, messageBody/*Object*/);

The registerMessageListener and unregisterMessageListener functions serve the purpose of registering and unregistering message listeners for specific windows and message types. Message filtering and dispatching is taken care of by the message service. The sendMessage function is used to send a message of the given type with the given message body to the given window. If an optional callback function is specified, the message service automatically takes care of two things:

  • It includes a unique replyWith header in the message
  • It keeps track of an incoming response to this message and dispatches the response to the callback accordingly.

The sendResponse function is used to send a response to a previously received message. The inReplyTo header needs to be set to the value of the replyWith header of the corresponding request.

In an interactive scenario between different Web applications in separate frames, each application has to maintain its own message service. However, in some simple cases a message service might not be necessary. Typically the host application has to keep track of multiple windows, message types and response callbacks and thus demands a message service. For a guest application it might be easier to use the window.postMessage API directly for the few message listeners it needs and the few messages it sends.

Protocol Injection

The question remains how host and guest applications ‘fit together’ in terms of message types and request-response patterns. As guest applications are external applications, they are typically not designed to communicate with the exact type and version of the host application they are embedded in. In our Studio example from above, the preview application is completely independent from the Studio and it does not know how to communicate with it’s host application. Consequently, the ability to cooperate with the Studio has to be added at runtime.

To tackle this problem and especially to avoid major modifications to guest applications, we have devised a general initialization protocol where the host application ‘injects’ the necessary behaviour into the guest application. The schema of the protocol is shown in the picture below.

initProtocol

Initialization Protocol

On the guest application side, a small block of bootstrapping code is required to run. After loading the Website, this code looks out for a parent window and sends an init request, containing the window/application type.

The host application then determines which behaviour is expected from a guest application of the given type and sends an initConfirm message. This message carries information about the expected guest application’s behaviour. In the simplest case this can be a URL to an application protocol script that implements this behaviour. The guest application just needs to load the script to get ready for interaction with it’s host application. From here on, message passing between host and guest applications ensues in accordance to the established application protocol. The first step always consists of a ready message from the guest application. Besides serving as a signal to the host application, it is convenient to include meta-data or information about the guest application’s initial state.

Wrap-up

Even though the same-origin policy is a rather challenging element in a multi web application mashup scenario, it is the only instrument to stop a vulnerability propagation from one application to another. A well-defined messaging interface allows interaction between applications while the same-origin policy can still be used to enforce separation.

A messaging service based on the HTML5 window.postMessage API is used to provide a high level API including basic conversation support and multiple window handling. The problem of diverting messaging protocol versions in interacting applications is tackled by protocol injection.

From → Dev, Tips & Tricks

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s