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
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.
Server-side (recommended)
// 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();
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();
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
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:
| Permission | Required | Purpose |
|---|---|---|
allow-scripts | Yes | Runs the Molecule React application |
allow-same-origin | Yes | Allows secure storage access |
allow-forms | Yes | Enables form submissions |
allow-popups | Yes | Allows modal dialogs |
<iframe sandbox="allow-scripts allow-same-origin allow-forms allow-popups" ...>
Do not add allow-top-navigation — this would allow the embedded page to navigate your parent page.
Troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
| Blank iframe | CORS not configured | Contact Molecule Systems to add your domain to the allowlist |
| Token POST fails | Wrong credentials or identity server unreachable | Check username, password, and identity server URL |
| Page loads but shows login screen | Token not received by handshake | Ensure postMessage fires after the iframe load event |
postMessage silently fails | Wrong MOLECULE_ORIGIN value | Origin must be exact — no trailing slash, correct protocol |
| Page loads with full navigation | Session not set as embedded | Ensure the handshake page URL is correct |
| Token expires mid-session | Long-running embed without refresh | Implement token refresh as shown above |
Security Checklist
Before going live, confirm the following:
- Credentials are stored server-side — never hardcoded in frontend JavaScript
-
postMessageuses the exactMOLECULE_ORIGIN, not'*' - Your domain has been added to the Molecule CORS allowlist
- The iframe
sandboxattribute does not includeallow-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