AuthenSeeDocs

Embed (Popup Drop-in)

Drop in @rebellion-systems/authensee-embed and launch the co-branded hosted flow in a popup for enrollment, authentication, and recovery.

Embed (Popup Drop-in)

@rebellion-systems/authensee-embed is a framework-agnostic drop-in that launches the AuthenSee hosted flow in a popup. You mint a session on your backend, call AuthenSee.open() to open the popup, and relay the one-time result back to your page from your callback URL.

The popup is the recommended surface. A popup is a top-level browsing context, which is the only place the WebAuthn passkey ceremony works across browsers — navigator.credentials.create() (passkey registration) and discoverable get() are blocked or restricted inside cross-origin iframes (Safari blocks them; Chrome only allows create() since v119 with a permission policy). Enrollment and recovery therefore require the popup.

Install

pnpm add @rebellion-systems/authensee-embed
# (npm and yarn work too)

Or load the IIFE/CDN build, which exposes a global window.AuthenSee:

<script src="https://cdn.authensee.com/v1/authensee.js"></script>
<script>
  AuthenSee.open({ flowUrl, onComplete });
</script>

The package ships ESM, CJS, and IIFE builds.

How it works

Your page                Your backend            AuthenSee popup        Auth Server
  |                          |                          |                    |
  |  1. open() (on click)    |                          |                    |
  |     pops a blank window  |                          |                    |
  |                          |                          |                    |
  |  2. flowUrl() mints a session                        |                    |
  |  ----------------------> | POST /v1/sessions        |                    |
  |                          | (x-api-key: sk_...)  ---- | -----------------> |
  |                          | <-- { hostedUrl, ... } -- | ------------------ |
  |  <-- hostedUrl ----------|                          |                    |
  |                          |                          |                    |
  |  3. popup navigates to hostedUrl  ---------------->  |                    |
  |                          |        user enrolls / authenticates (popup)    |
  |                          |                          | --- ZK proof ----> |
  |                          |                          |                    |
  |  4. popup lands on your callbackUrl?authResultCode=…|                    |
  |     callback page calls relayCallback()             |                    |
  |  <-- BroadcastChannel("authensee") -----------------|                    |
  |                          |                          |                    |
  |  5. onComplete({ authResultCode }) fires; popup closes                   |
  |                          |                          |                    |
  |  6. exchange authResultCode server-side             |                    |
  |  ----------------------> | exchange with sk_...     |                    |

Step 1: Mint a session and open the popup

Call open() inside a user gesture (a click handler) so the browser doesn't block the popup. Pass flowUrl as a function so the popup opens immediately and is navigated once your mint resolves — your backend creates the session and returns the hostedUrl from POST /v1/sessions.

import { open } from '@rebellion-systems/authensee-embed';
 
button.addEventListener('click', () => {
  open({
    // A function is opened gesture-safely: the popup opens now and is
    // navigated once the URL resolves.
    flowUrl: async () => {
      const res = await fetch('/api/authensee/mint', { method: 'POST' });
      const { hostedUrl } = await res.json();
      return hostedUrl;
    },
    onComplete: ({ authResultCode, linked, providerSubject, sessionId }) => {
      // Exchange authResultCode server-side with your secret key.
      exchangeServerSide(authResultCode);
    },
    onError: ({ code, message }) => {
      // code is "POPUP_BLOCKED" when the browser blocked the popup.
      console.error(code, message);
    },
  });
});

Your backend creates the session with its secret key and passes the callbackUrl (your callback page) so the popup knows where to land:

// POST /api/authensee/mint (your backend)
app.post('/api/authensee/mint', async (req, res) => {
  const r = await fetch('https://api.authensee.com/v1/sessions', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.AUTHENSEE_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      scope: 'full',
      externalUserId: req.user.id,
      callbackUrl: 'https://myapp.com/authensee/callback',
    }),
  });
  const session = await r.json();
  // session.hostedUrl → https://auth.authensee.com/flow/{flowCode}
  res.json({ hostedUrl: session.hostedUrl });
});

POST /v1/sessions returns a one-time flowCode and the ready-to-use hostedUrl (https://auth.authensee.com/flow/{flowCode}). The flow code is single-use — see the API reference for the full response.

Step 2: 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.

import { relayCallback } from '@rebellion-systems/authensee-embed';
 
// On https://myapp.com/authensee/callback
relayCallback();

The result fields delivered to onComplete are:

FieldTypeDescription
authResultCodestring | nullOne-time auth result code — exchange it server-side with your secret key. null for enroll-only flows.
linkedstring | nullPresent ("1") when an existing persona was linked rather than newly enrolled.
providerSubjectstring | nullAuthenSee's provider-scoped stable alias for the persona, when applicable.
sessionIdstringThe AuthenSee session ID this flow ran under.

open() options

OptionTypeRequiredDescription
flowUrlstring | (() => string | Promise<string>)YesThe hosted-flow URL, or a function returning it (e.g. an async mint). A function is opened gesture-safely.
onComplete(result) => voidYesCalled when the flow completes successfully.
onError(error) => voidNoCalled on error or when the popup is blocked (code: "POPUP_BLOCKED").
featuresstringNoOverride the popup window features (size, chrome).

open() returns a handle with close() and focus() methods.

Iframe mount (authentication-only)

mount(target, options) renders the flow inline in an iframe. It is authentication-only and only suitable for browsers that allow cross-origin publickey-credentials-get: a cross-origin iframe cannot run passkey registration (and discoverable get() is restricted), so it cannot do enrollment or recovery. Prefer open().

import { mount } from '@rebellion-systems/authensee-embed';
 
const handle = mount('#authensee-container', {
  flowUrl: 'https://auth.authensee.com/flow/flow_...',
  onComplete: ({ authResultCode }) => exchangeServerSide(authResultCode),
  onError: ({ message }) => console.error(message),
});
 
// later
handle.destroy();

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 for the co-brand model.

Next steps

  • Hosted pages guide — the redirect-based flow and the cookie-free hosted runtime
  • Theming — the co-brand model and theme fields
  • API referencePOST /v1/sessions, GET /v1/sessions/current

On this page