Skip to main content

Embedding Molecule Pages

This guide explains how to embed Molecule application pages into your own website using iframes. Authentication is handled by passing a bearer token through a lightweight handshake page, ensuring the embedded content loads fully authenticated without any visible delay.


Overview

1. Your page authenticates with the Molecule Identity Server
2. A bearer token is returned
3. Your iframe loads the Molecule handshake page
4. Your page sends the token to the handshake page via postMessage
5. The handshake stores the token and redirects to the real Molecule page
6. The Molecule page loads fully authenticated

Prerequisites

Before you begin you will need:

  • A Molecule user account with the appropriate permissions for the pages you want to embed
  • Your Identity Server URL — provided by Molecule Systems (e.g. https://id.moleculesystems.com)
  • Your Client ID — provided by Molecule Systems (e.g. molecule-api)
  • Your Application URL — the base URL of the Molecule application (e.g. https://admin.moleculesystems.com)
  • Your domain added to the Molecule CORS allowlist — contact Molecule Systems before testing
info

Contact Molecule Systems at support@moleculesystems.com to request CORS access for your domain and to confirm which pages are available for embedding.


Step 1: Authenticate with the Identity Server

Your page must first obtain a bearer token by calling the Molecule Identity Server. This should be done server-side where possible to keep credentials out of the browser.

// Node.js example — call from your backend, not the browser
const response = await fetch('https://id.moleculesystems.com/connect/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'password',
username: 'your-service-account@example.com',
password: 'your-password',
client_id: 'molecule-api',
}),
});

const { access_token, expires_in } = await response.json();
// Pass access_token to your frontend securely

Client-side (development and testing only)

const response = await fetch('https://id.moleculesystems.com/connect/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'password',
username: 'your-username@example.com',
password: 'your-password',
client_id: 'molecule-api',
}),
});

const { access_token, expires_in } = await response.json();
warning

Never hardcode credentials in client-side JavaScript for production. Use a backend service to authenticate and return the token to your frontend.


Step 2: Load the Handshake Page

Instead of loading the Molecule page directly into the iframe, load the Molecule handshake page first. This lightweight page receives your token, stores it, then redirects to the real page — ensuring the app has the token before it renders anything.

const MOLECULE_APP_URL = 'https://admin.moleculesystems.com';

// The real Molecule page you want to embed
const targetPath = '/app/edit-time-of-use/your-id-here';

// Build the handshake URL with the real page as a redirect parameter
const handshakeUrl = new URL(
`${MOLECULE_APP_URL}/static/embedded/embed-handshake.html`
);
handshakeUrl.searchParams.set('redirect', targetPath);

// Load the handshake page into your iframe
document.getElementById('molecule-frame').src = handshakeUrl.toString();
tip

The handshake page is a tiny HTML file — it loads in milliseconds and has no visible content. Your users will not see it.


Step 3: Send the Token via postMessage

Once the iframe fires its load event, send the token to it using postMessage. You must specify the exact Molecule application origin as the target — never use '*'.

const MOLECULE_ORIGIN = 'https://admin.moleculesystems.com';

const iframe = document.getElementById('molecule-frame');

iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage(
{
type: 'AUTH_TOKEN',
accessToken: access_token,
tokenType: 'Bearer',
},
MOLECULE_ORIGIN
);
}, { once: true }); // once:true ensures token is only sent for the handshake load
warning

Never use '*' as the target origin in postMessage. Always specify the exact Molecule application URL to prevent token interception.


Complete Example

Here is a full working example in plain HTML and JavaScript:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Site — Molecule Embed</title>
<style>
#molecule-frame {
width: 100%;
height: 800px;
border: none;
}
</style>
</head>
<body>

<iframe
id="molecule-frame"
title="Molecule"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
></iframe>

<script>
const MOLECULE_APP_URL = 'https://admin.moleculesystems.com';
const MOLECULE_ORIGIN = 'https://admin.moleculesystems.com';
const IDENTITY_URL = 'https://id.moleculesystems.com';
const CLIENT_ID = 'molecule-api';

// The Molecule page you want to embed
const targetPath = '/app/edit-time-of-use/your-id-here';

async function loadMoleculePage() {
// Step 1: Authenticate
const tokenResponse = await fetch(`${IDENTITY_URL}/connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'password',
username: 'your-username@example.com',
password: 'your-password',
client_id: CLIENT_ID,
}),
});

const { access_token } = await tokenResponse.json();
if (!access_token) {
console.error('Authentication failed');
return;
}

const iframe = document.getElementById('molecule-frame');

// Step 2: Build handshake URL
const handshakeUrl = new URL(
`${MOLECULE_APP_URL}/static/embedded/embed-handshake.html`
);
handshakeUrl.searchParams.set('redirect', targetPath);

// Step 3: Send token once handshake loads
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage(
{
type: 'AUTH_TOKEN',
accessToken: access_token,
tokenType: 'Bearer',
},
MOLECULE_ORIGIN
);
}, { once: true });

iframe.src = handshakeUrl.toString();
}

loadMoleculePage();
</script>

</body>
</html>

React / Next.js Example

import { useEffect, useRef, useState } from 'react';

const MOLECULE_APP_URL = 'https://admin.moleculesystems.com';
const MOLECULE_ORIGIN = 'https://admin.moleculesystems.com';
const IDENTITY_URL = 'https://id.moleculesystems.com';
const CLIENT_ID = 'molecule-api';

export default function MoleculeEmbed({ targetPath }) {
const iframeRef = useRef(null);
const [token, setToken] = useState(null);

// Step 1: Authenticate on mount
// In production, call your own backend endpoint instead
useEffect(() => {
async function authenticate() {
const resp = await fetch(`${IDENTITY_URL}/connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'password',
username: process.env.NEXT_PUBLIC_MOLECULE_USER,
password: process.env.NEXT_PUBLIC_MOLECULE_PASS,
client_id: CLIENT_ID,
}),
});
const data = await resp.json();
if (data.access_token) setToken(data.access_token);
}
authenticate();
}, []);

// Step 2 & 3: Load handshake and send token
useEffect(() => {
if (!token || !iframeRef.current) return;

const handshake = new URL(
`${MOLECULE_APP_URL}/static/embedded/embed-handshake.html`
);
handshake.searchParams.set('redirect', targetPath);

const iframe = iframeRef.current;

const onLoad = () => {
iframe.contentWindow.postMessage(
{ type: 'AUTH_TOKEN', accessToken: token, tokenType: 'Bearer' },
MOLECULE_ORIGIN
);
};

iframe.addEventListener('load', onLoad, { once: true });
iframe.src = handshake.toString();

return () => iframe.removeEventListener('load', onLoad);
}, [token, targetPath]);

return (
<iframe
ref={iframeRef}
title="Molecule"
style={{ width: '100%', height: '800px', border: 'none' }}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
/>
);
}

// Usage:
// <MoleculeEmbed targetPath="/app/edit-time-of-use/your-id-here" />

Token Lifetime and Refresh

Bearer tokens expire after a set period. Schedule a re-authentication before expiry:

const { access_token, expires_in } = await tokenResponse.json();

// Re-authenticate 60 seconds before the token expires
setTimeout(() => {
loadMoleculePage();
}, (expires_in - 60) * 1000);

Sandbox Permissions

The iframe sandbox attribute must include these permissions:

PermissionRequiredPurpose
allow-scriptsYesRuns the Molecule React application
allow-same-originYesAllows secure storage access
allow-formsYesEnables form submissions
allow-popupsYesAllows modal dialogs
<iframe sandbox="allow-scripts allow-same-origin allow-forms allow-popups" ...>
warning

Do not add allow-top-navigation — this would allow the embedded page to navigate your parent page.


Troubleshooting

ProblemLikely causeSolution
Blank iframeCORS not configuredContact Molecule Systems to add your domain to the allowlist
Token POST failsWrong credentials or identity server unreachableCheck username, password, and identity server URL
Page loads but shows login screenToken not received by handshakeEnsure postMessage fires after the iframe load event
postMessage silently failsWrong MOLECULE_ORIGIN valueOrigin must be exact — no trailing slash, correct protocol
Page loads with full navigationSession not set as embeddedEnsure the handshake page URL is correct
Token expires mid-sessionLong-running embed without refreshImplement token refresh as shown above

Security Checklist

Before going live, confirm the following:

  • Credentials are stored server-side — never hardcoded in frontend JavaScript
  • postMessage uses the exact MOLECULE_ORIGIN, not '*'
  • Your domain has been added to the Molecule CORS allowlist
  • The iframe sandbox attribute does not include allow-top-navigation
  • Token refresh is implemented for long-running sessions
  • HTTPS is used for all URLs — your site, the identity server, and the Molecule application