Skip to main content
Version: 3.0

Login via a Parent Application (iframe Bearer Token)

Operaide can be embedded as an iframe inside another web application. The parent application holds its own session with the user, generates a bearer token, and delivers that token to the iframe via postMessage. Operaide validates the token against an HTTP backend you control, which returns OIDC-compatible claims, and signs the user in. No separate login screen is shown inside the iframe.

Useful when Operaide is one panel in a larger product and you do not want users to sign in twice. The parent application owns identity. Operaide follows.

Prerequisites

  • A parent web application that embeds Operaide in an iframe and can send postMessage events.
  • An HTTP endpoint you control that accepts Authorization: Bearer <token> and returns OIDC-compatible claims.
  • The parent application's exact origin (scheme + host + port).
  • AUTH_BEARER_* environment variables editable on the Operaide deployment.

What the parent application must do

The parent is the driver. The iframe follows its instructions.

  1. Embed Operaide in an iframe: <iframe src="https://your-operaide.example.com/"></iframe>.

  2. Listen for AUTH_READY messages from the iframe. The iframe emits this once, per whitelisted origin, after its login handler is loaded.

  3. When ready, send a bearer token:

    iframe.contentWindow.postMessage(
    { type: 'AUTH_TOKEN', token: '<your-bearer-token>' },
    'https://your-operaide.example.com'
    );
  4. To sign the user out, send:

    iframe.contentWindow.postMessage({ type: 'LOGOUT' }, 'https://your-operaide.example.com');

The iframe rejects any message whose event.origin is not in the configured AUTH_BEARER_ALLOWED_ORIGINS list.

What your backend must expose

Operaide sends a GET request to the configured AUTH_BEARER_PROFILE_URL with Authorization: Bearer <token>.

On valid token, respond with 200 OK and a JSON body carrying OIDC-compatible claims:

{
"sub": "a-stable-unique-id-for-this-user",
"email": "alice@company.com",
"name": "Alice Example",
"given_name": "Alice",
"family_name": "Example",
"groups": ["engineering", "admins"]
}

Required fields: sub, email. Optional: name, given_name, family_name, groups. Same contract as External Password Login.

On invalid token, respond with 401. Any non-2xx status is treated as authentication failed.

How the backend validates the token (JWT verification, session lookup in a cache, opaque-token introspection) is up to you.

Configure Operaide

Set these environment variables on the deployment:

AUTH_BEARER_ENABLED=true
AUTH_BEARER_PROFILE_URL=https://your-backend.example.com/profile
AUTH_BEARER_ALLOWED_ORIGINS=https://parent.example.com

Multiple parent origins: comma-separated, no spaces.

AUTH_BEARER_ALLOWED_ORIGINS=https://parent-a.example.com,https://parent-b.example.com

Restart the container. Loading Operaide inside a whitelisted parent now drives login via postMessage.

Parent-side sequence

Parent                                           Operaide (iframe)

│ <iframe src="/">
│──────────────────────────────────────────────►
│ iframe loads, client bundle executes,
│ bearer_login listener registers.
│ For each allowed origin:
│ postMessage { type: 'AUTH_READY' } ◄────────

│ postMessage({ type: 'AUTH_TOKEN', token })
│──────────────────────────────────────────────►
│ Server calls profile URL,
│ validates, provisions user,
│ iframe is now logged in.

│ postMessage({ type: 'LOGOUT' })
│──────────────────────────────────────────────► Meteor.logout()

The parent does not need to wait for AUTH_READY before sending the first token, but using the signal avoids a race during page load.

Identity stays stable across token changes

Operaide matches users on every sign-in by sub first, email second. The internal account survives token rotation and email rename (see External Password Login for the details of the emails array sync). This applies to iframe login in the same way.

First sign-in attaches to an organization

First-time iframe sign-in creates the Operaide account and attaches it to an organization via the same auto-provisioning rule as SSO and External Password Login. See Auto-Provisioning.

Troubleshooting

Iframe never fires AUTH_READY at the parent. Either AUTH_BEARER_ENABLED is not true, or the parent's origin is not in AUTH_BEARER_ALLOWED_ORIGINS. The iframe posts AUTH_READY only to whitelisted origins. Check the exact scheme, host, and port.

Parent sends AUTH_TOKEN but the iframe stays on the login page. Same cause. The iframe's listener ignores messages from non-whitelisted origins without logging (to avoid leaking which origins are valid).

"Authentication failed (status 401)". Your backend rejected the token at the profile URL. Check the backend's logs.

"Auth backend claims invalid: <field>: <reason>". The backend returned 200 but the JSON body does not match the required schema. Fix the backend's response.

"Auth backend not reachable". Network-level failure between Operaide and the profile URL. Check container networking and AUTH_BEARER_PROFILE_URL.

Login succeeds but Operaide rejects with "No Organization found". See Auto-Provisioning: When auto-provisioning fails silently.

Limitations

  • One profile URL per deployment. AUTH_BEARER_PROFILE_URL is a single value. Dispatching to multiple backends happens inside your own backend, not in Operaide.
  • No token refresh flow in Operaide. Operaide uses the token once, at sign-in, to fetch claims. The resulting session is managed by Operaide on its own lifecycle. When the parent's token is rotated, the parent re-sends AUTH_TOKEN to force a re-sign-in.
  • No group-to-role mapping. groups is stored on the user record but does not drive Operaide roles yet.
  • Origin whitelist is strict-equality. No wildcards, no regex. Each parent origin must be listed explicitly.

Common mistakes

  • Using the email as sub. sub must be stable. If the backend's user identifier is an employee id, database primary key, or LDAP objectGUID, use that as sub. Email goes in email.
  • Missing the port in AUTH_BEARER_ALLOWED_ORIGINS. https://parent.example.com and https://parent.example.com:443 are treated as different origins by the browser only if a non-default port is used. Be explicit.
  • Expecting AUTH_TOKEN to pass through to your backend unchanged. Operaide forwards the token to AUTH_BEARER_PROFILE_URL via a server-side request. The browser never sends the token directly to your backend.
  • Leaking the token on the parent side. The parent is the security boundary. If the parent page itself is compromised (XSS, untrusted third-party script), the token can be exfiltrated before Operaide ever sees it.