> ## Documentation Index
> Fetch the complete documentation index at: https://docs.curator.interworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manual Embed - (iFrames)

> Embed content from external websites and legacy systems using iFrames and custom embed code.

Sometimes you need to go beyond Curator's standard embedding capabilities and embed content from your unique or legacy
systems into Curator. That's where Curator's **Manual Embeds** feature comes to the rescue! We'll walk you through the
steps to seamlessly embed any other website, using iFrames, into your Curator pages!

**Important Considerations:**

* While you're adding Manual Embeds, keep in mind that the embed code should come from trusted sources. This will help
  you avoid any security risks or funky user experiences.
* Some systems might require some extra configuration or customization to display perfectly within an iFrame. Check out
  the documentation or support resources for the specific system you're embedding to ensure compatibility and the best
  possible display.
* Don't forget to pay attention to the dimensions and aspect ratio of the iFrame content. We want it to fit smoothly
  within your Curator page. Adjust those dimensions if needed, so you avoid any cropping or weird distortions.

## Adding a Manual Embed to a Page

1. Login to the backend of your Curator instance (e.g. `https://www.curatorexample.com/backend` ).
2. Navigate to the **Content** > **Pages** section from the left-hand menu.
3. Find the page you'd like to add your Manual Embed to, or click the "+ New Page" button.
4. On the page builder editor, click the section you'd like to add your embed to, or click the "+" icon on the page
   builder page to add a new section.
5. Click the "Additional Elements" tab from the "Add Element" popup and select the **Embed** content type.
6. Choose the dropdown option that best fits your need from the options below
   * iFrame: For use when pasting in a simple URL (e.g. `https://mycuratorexample.com/embed`)
   * Embed Code (HTML): For use when copying embed code from another website - this will be in HTML and can be
     identified by searching the copied code for `</iframe>` - if this string is found in your code it means you are using
     an iFrame to embed.
7. Once you've pasted the correct information in, click "Add"

## Fitting the Frame to Its Content

A frame has one fixed height by default. Responsive content is often taller on a phone than on a desktop, so a fixed
height either leaves a scrollbar inside the frame on small screens or empty space below the content on large ones.
To have the frame resize to fit its content instead, select the element in the page builder and turn on **Fit Height
to Content** in the side panel. The frame then follows its content's height at every screen size, and again whenever
the content changes, up to 20,000 pixels.

**Fit Height to Content** is available on **iFrame** elements and on **File** elements with the File Type set to
**Embed**.

* **Embedded HTML files** need nothing else. Curator measures the file's height for you.
* **iFrame URLs** load another website, and browsers do not let Curator measure another website's page. The embedded
  page has to send its own height to Curator, so only turn this on for a page you or your team control. Add this
  script to that page:

```html theme={null}
<script>
(function () {
    function report() {
        var height = Math.ceil(Math.max(
            document.documentElement.getBoundingClientRect().height,
            document.body ? document.body.scrollHeight : 0
        ));
        window.parent.postMessage({type: 'curator:resize', height: height}, '*');
    }
    window.addEventListener('load', report);
    window.addEventListener('message', function (event) {
        if (event.data && event.data.type === 'curator:resize-request') {
            report();
        }
    });
    new ResizeObserver(report).observe(document.documentElement);
}());
</script>
```

Without that script, an iFrame with **Fit Height to Content** turned on keeps its normal height. Third-party
applications such as Sigma, Looker, SSRS, and Box do not send their height, so this setting is not offered for their
elements.

Avoid sizing the embedded content from the browser window, such as `height: 100vh`. Inside a frame, the window is the
frame itself, so that content grows every time the frame grows. Curator detects this after a moment, stops resizing
the frame, and puts it back to its normal height. For the same reason, Fit Height to Content does not work on an
iFrame that shows another Curator page, because Curator pages stretch to fill the window.

## URL Parameter Substitution

When you use the **iFrame** option (a plain URL), Curator can substitute the following placeholders in the URL
with the signed-in user's details. Substitution happens server-side, only when a user is logged in, and only for
the iFrame option — placeholders are **not** processed in the Embed Code (HTML) option.

| Placeholder | Replaced with |
| - | - |
| `[USERNAME]` | The logged-in user's Curator username (often their email address) |
| `[DISPLAY_NAME]` | The logged-in user's display name |
| `[IW_TOKEN]` | A short-lived, cryptographically signed identity token for the logged-in user (see [Passing a verified identity with `[IW_TOKEN]`](#passing-a-verified-identity-with-iw_token) below) |

Placeholder matching is case-insensitive. For example, `https://myapp.example.com/report?user=[USERNAME]` renders
as `https://myapp.example.com/report?user=jane.smith@company.com` for the signed-in user. This lets the embedded
application receive the current user's identity in the URL without Curator forwarding any session tokens.

`[USERNAME]` and `[DISPLAY_NAME]` are plain strings, so an embedded application cannot tell whether they were
genuinely set by Curator or forged by whoever crafted the URL. When the embedded application needs to *trust* the
identity — not just display it — use `[IW_TOKEN]` instead.

## Passing a verified identity with `[IW_TOKEN]`

`[IW_TOKEN]` substitutes a short-lived JSON Web Token (JWT) that the embedded application can cryptographically
verify, so it can trust *which Curator user* is viewing the embed without any login flow inside the iframe and
without Curator forwarding session cookies. Append it to the iFrame URL as a query parameter, for example:

```text theme={null}
https://myapp.example.com/form?iw_token=[IW_TOKEN]
```

At render, for a logged-in user, Curator replaces `[IW_TOKEN]` with a freshly signed token. The token:

* is signed with RS256 and lasts about **60 seconds**, so it must be verified as soon as the embed loads;
* carries **identity only** — the user's id (`sub`), username (`preferred_username`), display name (`name`), and
  email (`email`, when the user has one). It carries **no groups or roles**, so the embedded application remains
  responsible for its own authorization;
* is **scoped to the embed's host** (the `aud` claim is the iFrame URL's host), so a token minted for one embed
  cannot be replayed against a different application;
* includes a unique token id (`jti`) the embedded application should record to enforce single use;
* names Curator's JWKS URL in the `jku` claim, so the application does not have to hard-code or reconstruct it.

The embedded application verifies the token against Curator's public keys, published as a JWKS (JSON Web Key Set).
Read the JWKS URL from the token's `jku` claim rather than assuming a fixed location — it is the endpoint below,
and it already accounts for portals hosted under a base path (e.g. `https://<host>/<basepath>/user/embedauth/jwks.json`):

```text theme={null}
https://<your-curator-host>/user/embedauth/jwks.json
```

The token's header names the key (`kid`) used to sign it; the application matches that against the JWKS, checks
the signature, and confirms the `iss` (your Curator URL), `aud` (its own host), and expiry before trusting the
identity. The JWKS endpoint is public and unauthenticated — it exposes only public keys — so the embedded
application can fetch it without a Curator session. No Curator-side configuration is required — signing keys are
created automatically the first time a token is minted.

`[IW_TOKEN]` is only substituted for the **iFrame** option and only when a user is logged in; for an anonymous
visitor it resolves to an empty value. As with the other placeholders, it is not processed in the Embed Code
(HTML) option.

## Authentication Limitations

Curator's iFrame embed renders the target URL inside a browser frame — it does not broker authentication between
Curator and the embedded application. Specifically, Curator does **not**:

* Forward Curator session cookies or authentication tokens to the embedded application.
* Intercept HTTP 3xx authentication redirects that happen inside the iframe.
* Detect when an iframe is displaying a login page instead of the intended content.

If the embedded application has its own authentication requirement (for example, an OIDC/OAuth login flow), users
who are not already authenticated to that application will see the application's own login UI inside the iframe.
Whether the iframe renders at all depends on the target application's own framing policy:

* `X-Frame-Options: DENY` — The application refuses all iframe embedding; the frame appears blank.
* `X-Frame-Options: SAMEORIGIN` — The application only allows framing from its own origin, so it will not load
  inside Curator (a different origin). The frame appears blank.
* A `Content-Security-Policy` header on the target — The browser may block the frame depending on the policy.

When you add or edit an iFrame URL in the **page builder**, Curator checks the target URL's response headers and
warns you in the editor if the target appears to block framing (an `X-Frame-Options` of `DENY`, a `SAMEORIGIN`
value that doesn't match your Curator host, or the presence of a `Content-Security-Policy` header). This check is
a build-time convenience in the editor; it does not run when visitors view the published page, so always confirm
the embed renders correctly on the live page.

**Recommended approach for applications with their own authentication:**

1. Work with the target application's team to explicitly allow framing from your Curator domain — for example, by
   setting `Content-Security-Policy: frame-ancestors 'self' https://analytics.yourcompany.com` on the target
   application.
2. Ensure the target application's session cookies use `SameSite=None; Secure` so browsers transmit them in
   cross-site iframe requests.
3. If you control the target application, use the [`[IW_TOKEN]` placeholder](#passing-a-verified-identity-with-iw_token)
   to pass a signed, verifiable identity into the embed — the application verifies it against Curator's JWKS and
   establishes its own session for that user, with no inline login form and no redirect. This is the preferred
   option when the embed needs to *trust* the Curator user's identity (for example, an authenticated Streamlit form).
4. If the target application only supports a plain-string deep link, use the `[USERNAME]` placeholder to pass the
   user's identity to a token-exchange endpoint on the target side instead. Note that, unlike `[IW_TOKEN]`, a plain
   username is not verifiable, so only use it where the target endpoint applies its own trust checks.

There is no Curator-side configuration to open an authentication flow in a new tab and return to the embed once
authentication is complete.

## Content Security Policy and Framing Headers

Curator sets the following security-related response headers on **its own pages**. These govern how Curator pages
behave — they do not restrict which origins Curator's iframes may load from:

* **`X-Frame-Options: sameorigin`** — Controls whether Curator itself can be framed by another site. On rendered
  pages this header is omitted when the portal **Allow iframe embedding** setting is enabled, so that Curator can be
  embedded elsewhere.
* **`Content-Security-Policy: upgrade-insecure-requests`** — Applied when the portal **Force SSL** setting is
  enabled. It upgrades mixed HTTP/HTTPS sub-resource requests; it does not restrict iframe sources.
* **`X-Content-Type-Options: nosniff`** — Prevents MIME-type sniffing; it does not affect iframe loading.

Cross-origin embedding restrictions are enforced by the **target application's** headers, not by Curator's.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.