Embed (popup drop-in)
Launch the co-branded hosted flow in a popup with @rebellion-systems/authensee-embed — enrollment, authentication, and recovery without leaving your page.
What you'll build: a button on your page that opens the AuthenSee hosted flow in a popup, receives the result back on your page without a full-page navigation, and completes the login on your backend.
When to use this: any third-party web app. The popup is the recommended surface because the user never leaves your page, and because a popup is a top-level browsing context — the only place the WebAuthn passkey ceremony works across browsers. navigator.credentials.create() (registration) and discoverable get() are blocked or restricted inside cross-origin iframes, and the hosted flow refuses framing outright. If you'd rather hand off the whole page, use the redirect flow instead.
How it works
Install
Or load the IIFE/CDN build, which exposes a global window.AuthenSee:
The package ships ESM, CJS, and IIFE builds.
Mint a session on your backend
Your backend creates the session with its secret key and passes the callbackUrl (your callback page) so the popup knows where to land:
POST /v1/sessions returns a single-use flowCode inside the ready-to-use hostedUrl — see the sessions reference for the full request and response. Return only hostedUrl to the browser, never sessionToken.
Open the popup from a click handler
Call open() inside a user gesture so the browser doesn't block the popup. Pass flowUrl as a function: the popup opens immediately (gesture-safe) and navigates once your mint resolves.
Relay the result from your callback page
On the page you registered as callbackUrl, call relayCallback(). It reads the one-time result from the URL, publishes it to the opener over a same-origin BroadcastChannel named "authensee", and closes the popup.
The result fields delivered to onComplete:
| Field | Type | Description |
|---|---|---|
authResultCode | string | null | One-time result code — exchange it server-side with your secret key. null for enroll-only flows and for persona-link completions. |
linked | string | null | Present ("1") when an existing persona was linked rather than newly enrolled |
providerSubject | string | null | Your provider-scoped stable alias for the persona, when applicable |
sessionId | string | The AuthenSee session ID this flow ran under |
Exchange the result code on your backend
The code is single-use and short-lived; exchange it as soon as your backend receives it. See auth results for the full response and JWT claims.
open() options
| Option | Type | Required | Description |
|---|---|---|---|
flowUrl | string | (() => string | Promise<string>) | Yes | The hosted-flow URL, or a function returning it (e.g. an async mint). A function is opened gesture-safely. |
onComplete | (result) => void | Yes | Called when the flow completes successfully |
onError | (error) => void | No | Called on error or when the popup is blocked (code: "POPUP_BLOCKED") |
features | string | No | Override the popup window features (size, chrome) |
open() returns a handle with close() and focus() methods.
No iframe embedding
The hosted flow cannot be embedded in an iframe: it sends Content-Security-Policy: frame-ancestors 'none' on every route, so any iframe pointed at it is refused by the browser. The popup (open()) or a full-page redirect are the only supported integration surfaces.
Branding
The hosted flow is co-branded from your provider configuration: AuthenSee owns the frame and you contribute a logo, display name, one brand color, one copy line, and a light or dark surface. See theming.
Troubleshooting
The popup never opens (POPUP_BLOCKED). open() must be called synchronously inside a user gesture (a click handler). If you await anything before calling open(), browsers treat the popup as unsolicited — pass your async mint as the flowUrl function instead, and let open() run first.
Session creation fails with VALIDATION_ERROR: callbackUrl origin ... is not on this provider's allowlist. The origin of your callbackUrl (scheme + host + port, exactly) must be listed in your provider's allowed callback origins. An empty allowlist rejects every callback URL — add your origin in the dashboard before testing.
The user refreshed the popup and got "flow expired". The flowCode in the URL is single-use: it's redeemed for an in-memory token when the flow loads, and a full-page refresh can't redeem it again. Mint a fresh session and reopen the flow.
onComplete never fires. Confirm your callback page actually calls relayCallback(), and that the callback page is served from the same origin as the opener — the BroadcastChannel relay is same-origin by design.
The user got an "update your security setup" step they didn't ask for. Your factor-combination policy changed since the user enrolled, and their login hit 409 policy_upgrade_required. The hosted flow handles the upgrade ceremony automatically — the user proves their existing factor once and adds the new one. See the upgrade ceremony.
Exchanging the code fails with VALIDATION_ERROR. The code was already consumed (they're strictly single-use) or expired. Treat this as a failed login and start a fresh flow — never retry an exchange with the same code.
Next
- Hosted pages guide — the redirect form and the full ceremony narrative
- Sessions reference — every field on
POST /v1/sessions - Theming — the co-brand model