Security

Overview

The security topics related to using the io.Connect Browser platform can be divided into the following major categories:

  • Security management of the container in which io.Connect Browser operates (any modern web browser).
  • io.Connect Browser security settings and security specifics originating from its architectural design.

Web Browsers

io.Connect Browser is a collection of web libraries which you can use to build and deploy an interop-enabled web platform and run it in already existing third-party containers - all modern web browsers. This means that the platform itself doesn't have any control over the security settings of the container in which it operates.

In the general case, all security-related policies and browser settings are managed centrally by the enterprise in which io.Connect Browser is deployed. All clients are responsible for following, implementing, or applying the best security practices related to using browsers and web apps in their own environments. Configuring the security of the platform container is outside the scope of io.Connect Browser as a product and must be handled by clients internally based on their specific business needs and requirements.

An important side-effect of the fact that io.Connect Browser doesn't have security control over its container is that users may freely run an io.Connect Browser project in the same browser instance along with other third-party web apps that aren't part of the io.Connect platform. You should keep in mind that if the security of any of these apps becomes compromised, this could potentially lead to exposing the io.Connect platform to a security risk as well by means of poisoning the mutual container (e.g., if the compromised app is able to access the local storage or the cookies of other apps).

Platform Architecture & Settings

The following sections explain the security-related architectural specifics of io.Connect Browser and describe the available platform-wide security settings you can use to ensure the security of your platform deployment.

License Keys

The @interopio/browser-platform library (used for creating a basic Main app for your io.Connect Browser project) and the @interopio/home-ui-react library (used for creating a custom Home App - an enhanced Main app for io.Connect Browser projects) are publicly accessible in NPM. However, to be able to initialize these libraries and use io.Connect Browser, you must have a valid license key.

License keys are encrypted using secure encryption mechanisms. All license keys are bound to a specific client origin which will prevent running the platform from another origin in case a license key has been stolen from a client.

The following example demonstrates how to provide a license key when initializing the io.Connect Browser platform:

import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    // It's required to provide a valid license key.
    licenseKey: "my-license-key"
};

const { io } = await IOBrowserPlatform(config);

Authentication

Handling users and user authentication is outside the scope of io.Connect Browser as a product. io.Connect Browser is a web platform that can be integrated with any already existing authentication mechanisms in the client infrastructure.

ℹ️ The Home App provides facilities for integrating your io.Connect Browser platform with simple, Auth0, or custom authentication mechanisms. For more details, see the Capabilities > Home App > Library Features section.

You should initialize the io.Connect Browser platform only after you have authenticated the current user with your own authentication mechanisms. This way, you can pass any details about the already authenticated user when initializing the @interopio/browser-platform or the @interopio/home-ui-react libraries. While the io.Connect Browser platform isn't meant to handle user authentication and while providing user details when initializing the platform is entirely optional, it's still highly recommended to do so, because the platform can use them internally for caching and visualization purposes.

The following example demonstrates how to provide user details when initializing the io.Connect Browser platform:

import IOBrowserPlatform from "@interopio/browser-platform";
import authUser from "./my-user-auth";

// Use your own existing mechanism for authenticating users.
const { id, firstName, lastName, email } = await authUser();

const config = {
    licenseKey: "my-license-key",
    // Optional user details that may be used by the platform for caching and visualization purposes.
    user: {
        // If providing user details, it's required to specify at least an ID.
        id: "user-id",
        firstName: "John",
        lastName: "Doe",
        email: "john.doe@example.com"
    }
};

const { io } = await IOBrowserPlatform(config);

If you are using io.Manager as a store provider, you can pass details about the already authenticated user (username, password, token, and more) when configuring the connection to io.Manager:

import IOBrowserPlatform from "@interopio/browser-platform";
import authUser from "./my-user-auth";

// Use your own existing mechanism for authenticating users.
const { username, password } = await authUser();

const config = {
    licenseKey: "my-license-key",
    manager: {
        // URL pointing to io.Manager.
        url: "https://my-io-manager.com:4242/api",
        // Basic authentication details.
        auth: {
            basic: {
                username,
                password
            }
        }
    }
};

const { io } = await IOBrowserPlatform(config);

Connection

The Main app of io.Connect Browser is web app that uses the @interopio/browser-platform library and acts as a central hub for connecting all Browser Client apps with the io.Connect framework and with each other. All io.Connect operations are handled by the Main app and when a Browser Client connects to it, it gains access to all io.Connect functionalities.

One of the main security features provided by io.Connect Browser is the ability to control which Browser Client apps can connect to the Main app. Being able to control the connection to your Main app is extremely important as once connected to the io.Connect platform, a Browser Client app has full access to all io.Connect features related to sharing data between apps (Interop methods, shared contexts, Channels, and more), window and app management, and more.

To provide connection settings for your Main app, use the connection property of the configuration object for initializing the @interopio/browser-platform library.

The following example demonstrates defining a list of origins to block from connecting to the Main app:

import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    licenseKey: "my-license-key",
    connection: {
        // Array of origins or glob patterns describing origins to block from connecting to the platform.
        blockList: ["https://example.com/", "*.another-example.com/*"]
    }
};

const { io } = await IOBrowserPlatform(config);

The connection object has the following properties:

Property Type Description
allowedClientFallbackOrigin string The Main app will fallback to being a Browser Client only if the other Main app that is attempting to open it is hosted at the origin specified here. Valid only if alwaysPlatform is set to true.
alwaysPlatform boolean If true, the Main app won't fallback to being a Browser Client and won't initialize at all when another Main app attempts to open it. Defaults to false.
blockList string[] List of origins or glob patterns describing origins to block from connecting to the Main app.
preferred object Settings for connecting to io.Connect Desktop.

By default, the Main app will fall back to being a regular Browser Client app if it has been opened by another Main app. This is due to the fact that an io.Connect Browser project can have only one Main app at a time. This behavior may sometimes be undesirable, because when a Main app falls back to being a Browser Client, all its settings and configurations (including all security settings) are disregarded and it inherits the settings of the Main app that has opened it.

To prevent this behavior, use the alwaysPlatform and allowedClientFallbackOrigin properties of the connection object when initializing the @interopio/browser-platform library. The alwaysPlatform property determines whether the Main app should always remain a Main app. If alwaysPlatform is set to true, when another Main app attempts to open the current Main app as a Browser Client, the current Main app won't initialize at all. The allowedClientFallbackOrigin property can be used to specify an exception from this rule - an origin from which the Main app can be opened as a Browser Client if alwaysPlatform is set to true:

import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    licenseKey: "my-license-key",
    connection: {
        // This will prevent other Main apps from opening the current Main app as a regular Browser Client.
        alwaysPlatform: true,
        // If `alwaysPlatform` is set to `true` and this is specified, another Main app hosted at this origin
        // will be able to open the current Main app as a regular Browser Client.
        allowedClientFallbackOrigin: "https://my-allowed-client-fallback-origin.com/"
    }
};

const { io } = await IOBrowserPlatform(config);

Visual Integration

io.Connect Browser is a web platform that allows multiple web apps to operate together in a unified visual space (usually a Workspace or a Global Layout consisting of Workspaces and individual windows):

Workspaces

When participating in Workspaces, the instances of all interop-enabled apps are embedded as <iframe /> elements. This allows:

  • Seamless visual integration.
  • Consistent window and Layout management.
  • Controlled data sharing between apps.
  • Centralized access control and monitoring.

Each embedded app operates within its own isolated context while still participating in all shared interoperability features provided by the io.Connect platform.

The usage of <iframe /> elements is an intentional architectural decision which provides a controlled and secure way of visually integrating app windows within the platform. Inline frame elements are a proven, standard-based method for securely embedding web content. While using <iframe /> elements comes with potential security risks, they provide the following advantages and industry-proven mechanisms for mitigating these risks:

  • Strong isolation - each app embedded in an <iframe /> element operates in its own browser security context reducing the risk of cross-app interference.
  • Cross-origin compliance - <iframe /> elements work seamlessly with Cross-Origin Resource Sharing (CORS) policies to control and authenticate interactions across origins.
  • Granular control - Server-side Content Security Policy (CSP) headers allow precise limitation of what embedded apps can do.

Additionally, the io.Connect platform provides:

  • Security settings for managing the connection to the platform that enable you to control which Browser Client apps are allowed to connect to your Main app.
  • High-level interoperability APIs for the developers to use instead of complex low-level postMessage() calls via the browser API. This effectively mitigates the risk of insecure messaging patterns.
  • The ability to be integrated with any authentication mechanism preferred by the client for verifying user and app identity.

This ensures that all embedded content operates safely while enabling rich interoperability between trusted apps.

The architectural design choice for embedding apps in the io.Connect Browser Workspaces by using <iframe /> elements aligns with all modern web platform design patterns and is used by enterprise-grade systems such as Microsoft Teams, Salesforce, Atlassian products, and more.

Usage of <iframe /> elements may come with the following typical risks:

  • Clickjacking - when a malicious site embeds another page to trick users into unintended actions.
  • Cross-site scripting (XSS) - if framed content is untrusted or executes unsafe scripts.
  • Cross-origin data leakage - potential exposure of sensitive data via unrestricted postMessage() or script access.

In the context of the io.Connect platform, these risks are mitigated in the following ways:

  • Governed content - all framed apps are intentionally onboarded and governed by your organization (no arbitrary third-party content), reducing untrusted code risk.
  • High-level interoperability APIs - developers use high-level io.Connect APIs (such as io.interop.invoke(), io.contexts.update(), and more) instead of using directly the postMessage() method of the browser API, eliminating the risk of insecure messaging patterns.
  • Platform mediation - the io.Connect Browser platform mediates inter-app communication, performs schema validation, and (if configured) enforces a centrally managed origin block list during platform initialization and connection handshake.
  • CORS enforcement - cross-origin requests and responses are controlled via CORS headers, limiting interactions to allowed origins.
  • CSP enforcement - content security policies restrict sources for scripts, styles, and frames to trusted origins.
  • HTTP security headers - apps use server-side security headers to control capabilities and prevent unauthorized embedding.

Together, these measures ensure that <iframe /> element usage is secure and compliant with standard web security practices.

How Inline Frame Elements Are Used

The most important aspect of using <iframe /> elements in io.Connect Browser is that the Browser Client apps don't create or configure the <iframe /> elements directly. Inline frame elements are handled in the following way by the io.Connect platform:

  1. Browser Client apps provide only URLs - your organization configures the app URLs by using app definitions.

  2. The <iframe /> elements are created by the io.Connect platform when a user launches an app in a Workspace:

  • The platform creates the <iframe /> element dynamically.
  • The platform sets the src attribute to the URL of the app as configured in its app definition.
  • The platform manages the lifecycle of the <iframe /> element (creation, visibility, positioning, destruction).
  • The platform handles any window management and Layout operations.
  1. Security via HTTP response headers - since Browser Client apps don't control the <iframe /> attributes, all security controls must be implemented via HTTP response headers from your app server (e.g., CSP and CORS headers).

The following example demonstrates providing a definition for an app when initializing the @interopio/browser-platform library

import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    licenseKey: "my-license-key",
    applications: {
        local: [
            // Object describing an app to be included in the io.Connect platform.
            {
                name: "my-app",
                type: "window",
                title: "My App",
                details: {
                    // App URL.
                    url: "https://my-domain.com/my-app"
                }
            }
        ]
    }
};

const { io } = await IOBrowserPlatform(config);

When this app is launched in a Workspace by the user or programmatically via the io.Connect APIs, io.Connect Browser will create an <iframe /> element pointing to the specified app URL. The app server hosting the index.html file of the app must return the appropriate security headers.

App Server Security

Your apps must explicitly allow controlled, least-privilege cross-origin access when being embedded via <iframe /> elements in io.Connect Browser. This approach follows the OWASP and W3C best security practices: when you control both the platform and the embedded apps while enforcing CORS policies, common <iframe /> risks (such as clickjacking and XSS attacks from untrusted content) are effectively mitigated.

The following sections provide general guidelines on how to configure the HTTP response headers on your app server to prevent potential security risks:

Cross-Origin Resource Sharing

For any API or asset your framed app loads cross-origin, configure the CORS headers with explicit allow lists and credential handling as needed:

Header Description
Access-Control-Allow-Credentials Set to true only if you need cookie credentials or HTTP authentication. Pair with an Access-Control-Allow-Origin header that's set to a specific origin.
Access-Control-Allow-Headers Restrict to the HTTP headers your app actually needs. Respond to preflight (OPTIONS) requests correctly.
Access-Control-Allow-Methods Restrict to the HTTP methods your app actually needs. Respond to preflight (OPTIONS) requests correctly.
Access-Control-Allow-Origin Set to the exact origin (or origins) of your io.Connect Browser platform. Don't set to * when credentials or sensitive data are involved.

Content Security Policy

The architecture of io.Connect Browser architecture doesn't allow Browser Client apps to configure the attributes of <iframe /> elements. Instead, the platform creates and manages these elements itself when you provide app URLs. Security controls are enforced via HTTP response headers that must be set on your app server.

Your app server must configure the Content-Security-Policy header by using the following key directives in order to control how your app will behave when embedded:

Directive Description
connect-src Control which APIs your app can access via the browser XHR and Fetch APIs.
default-src 'self' Load resources only from your own origin by default.
frame-ancestors Specify which origins can embed your app in an <iframe />. This directive prevents your app from being embedded by unauthorized origins (clickjacking protection).
script-src Control from where your app can load scripts. Use nonces or hashes and avoid setting this to 'unsafe-inline'.

The following example demonstrates how to configure the Content-Security-Policy header with directives that control which origin can embed your app in an <iframe /> and the resources and scripts loaded by your app:

Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'nonce-<random-string>' https://trusted-cdn.example.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    font-src 'self' https://fonts.example.com;
    connect-src 'self' https://api.example.com;
    frame-ancestors https://my-platform.example.com;

Cache

To ensure correct browser caching behavior when working with multiple allowed origins, use the Vary: Origin response header.

Define a short and reasonable time frame for cached server responses in order to balance performance and flexibility by using the Cache-Control: max-age=<max-age-in-seconds> response header.

Cookies

If you need to use cookies in your embedded app, they must be set with the SameSite=None; Secure attributes in order for modern browsers to be able to send them in cross-site requests.

Interoperability Mechanisms

Communication and data sharing between apps participating in io.Connect Browser is executed via a secure interoperability layer - high-level interoperability APIs provided by the io.Connect platform. The io.Connect APIs eliminate the need of using complex low-level browser API calls (such as postMessage()) in order to establish communication between embedded apps or individual browser windows. The usage of these high-level APIs mitigates the risk of insecure messaging patterns.

Furthermore, the Main app mediates all communication between the Browser Client apps and enforces platform-wide connection settings (e.g., origin block list). This enhances the platform security by granting the platform administrators full control over which apps can connect to the io.Connect framework.

Establishing a Connection

When a Browser Client app initializes the io.Connect API, a dedicated messaging channel (logical port) is established between that app and the platform mediation layer. The app may be embedded in an <iframe /> element (e.g., participating in a Workspace) or it may be hosted in an individual browser window.

The following steps describe the handshake process for establishing a connection between a Browser Client app and the io.Connect framework:

  1. The app loads and attempts to initialize the io.Connect library by requesting a connection to the io.Connect framework.

  2. The io.Connect library performs a handshake request to the io.Connect platform (e.g., INIT_REQUEST message).

  3. The platform validates the app origin against the platform-wide block list configured by the platform administrators: blocked origins will be rejected; if a block list isn't configured, all origins will be able to establish a connection with the io.Connect framework.

  4. If the origin isn't blocked, the platform returns a response (e.g., INIT_ACK message) containing a connection identifier and protocol version.

  5. Subsequent messages initiated via the io.Connect APIs (e.g., Interop method invocations, shared context updates, Channel subscriptions) are sent over this logical port and are tagged with the connection identifier.

High-Level io.Connect APIs

As already stated, to achieve interoperability between the different apps within the platform, app developers use high-level io.Connect APIs instead of the raw postMessage() browser API. The io.Connect APIs provide a secure abstraction layer that works in unison with the platform mediation mechanisms and the platform-wide connection settings. This ensures seamless and secure communication between the Browser Client apps.

The following example demonstrates a Browser Client app subscribing for updates to a shared context by using the io.Connect Shared Contexts API:

const contextName = "my-context";
const handler = context => console.log(`Context updated: ${JSON.stringify(context)}`);

await io.contexts.subscribe(contextName, handler);

Under the hood, the io.Connect API operations are executed in the following way:

  1. The Browser Client app invokes an io.Connect API method (e.g., io.contexts.subscribe()).

  2. The io.Connect API sends an internal secure message to the platform via postMessage().

  3. The platform validates, processes, and routes the request:

  • Associates the message with the Browser Client app and enforces the origin block list (if configured).
  • Applies configured server-side security policies (CSP and CORS).
  • Routes the message.
  • Handles protocol versioning and backwards compatibility.
  1. The platform returns a secure response to the API via postMessage().

  2. After receiving a response from the platform, the io.Connect API returns a result to the Browser Client app via Promise objects and callbacks.

Resource Sharing for the Additional io.Connect Libraries

The Main app of io.Connect Browser shares across the Browser Client apps the resources (bundle, fonts, styles) necessary for using some of the additional io.Connect libraries like @interopio/modals-api, @interopio/intent-resolver-ui, and @interopio/widget.

To improve the security of your platform, you can instruct your Main app which Browser Clients to exclude from sharing these resources. Each of these additional libraries has a corresponding top-level key in the configuration object for initializing the @interopio/browser-platform library which you can use to configure the respective library.

The following example demonstrates how to configure in the Main app all additional io.Connect libraries that require resource sharing:

import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    licenseKey: "my-license-key",
    widget: {
        // These resources are shared among all Browser Clients.
        sources: {
            bundle: "https://my-widget/widget-bundle.js",
            styles: ["https://my-widget/styles.css", "https://example.com/custom-styles.css"]
            fonts: ["https://my-widget/fonts.css"]
        },
        // Origins of Browser Clients to block from using the widget.
        blockList: ["https://example.com/*", "https://another-example.com/*"]
    },
    intentResolver: {
        sources: {
            bundle: "https://my-intent-resolver/intent-resolver-bundle.js",
            styles: ["https://my-intent-resolver/styles.css", "https://example.com/custom-styles.css"],
            fonts: ["https://my-intent-resolver/fonts.css"]
        },
        // Origins of Browser Clients to block from using the Intent Resolver.
        blockList: ["https://example.com/*", "https://another-example.com/*"]
    },
    modals: {
        sources: {
            bundle: "https://my-modals/modals-bundle.js",
            styles: ["https://my-modals/styles.css", "https://example.com/custom-styles.css"],
            fonts: ["https://my-modals/fonts.css"]
        },
        // Origins of Browser Clients to block from using modal windows.
        blockList: ["https://example.com/*", "https://another-example.com/*"]
    }
};

const { io } = await IOBrowserPlatform(config);

Progressive Web Apps

The Main app of your io.Connect Browser project can be configured as a Progressive Web App.

If you decide to configure your Main app as a PWA, you should take into consideration an important architectural feature of the platform and the related implications - the Browser Client apps participating in the platform can be hosted at origins different from the origin of the Main app.

In the context of PWAs, this means that when your Main app is installed as a PWA and opens a Browser Client app hosted at a different origin, the newly opened window containing the Browser Client app will contain an additional UI element displaying the URL address bar and other browser controls.

This is a security feature implemented by the modern web browsers and its goal is to inform and orient the user that they are navigating outside the scope of your PWA. If you want to remove this additional UI element in order to optimize the real estate of the app window, you may use the approach described here.

⚠️ Note that it's generally not recommended to disable any browser security features. Displaying the URL address bar when the PWA navigates out of scope is such a feature and if you decide to disable it, you should carefully consider all related security risks. It's highly recommended to employ all other security controls provided by the platform (such as connection settings) in order to mitigate all potential risks of allowing untrusted origins access to the io.Connect framework.

A possible solution for removing the URL address bar when a PWA navigates out of scope is to implement a reverse proxy server hosted at your Main app origin. The purpose of the reverse proxy server will be to mask the origins of the Browser Client apps. Should you decide to implement this approach, you should carefully configure the reverse proxy server to mask only the origins of the known and trusted Browser Client apps.