AuthenSeeDocs

Hosted Pages

Integrate AuthenSee with zero frontend code using session-based hosted page redirects.

Hosted Pages

Hosted pages provide a session-based integration path. Create a session on your backend, bring the user into an AuthenSee-hosted flow, and receive a one-time result you exchange server-side.

You can bring the user into the hosted flow two ways:

  • Popup drop-in@rebellion-systems/authensee-embed opens the flow in a popup and relays the result back to your page. This is the recommended surface, and the only one that can run passkey enrollment and recovery (the WebAuthn ceremony requires a top-level context). See the embed guide.
  • Full redirect — redirect the browser to the hosted URL and handle a callback. Covered below.

Either way, your backend mints the session with its secret key and receives a one-time flowCode.

How it works

Your Backend              AuthenSee Hosted Page         Auth Server
|                             |                          |
|  1. POST /v1/sessions       |                          |
|     (x-api-key: sk_...)  ---|------------------------> |
|                             |                          |
|  <-- { sessionId,           |                          |
|        sessionToken,        |                          |
|        flowCode, hostedUrl }|                          |
|                             |                          |
|  2. Send user to hostedUrl: |                          |
|     /flow/{flowCode}        |                          |
|   ----------------------->  |                          |
|                             |                          |
|              3. Redeems the one-time flowCode          |
|                 POST /v1/hosted/flow-code/redeem ----> |
|                 <-- { sessionToken } (held in memory)  |
|                 GET /v1/sessions/current (Bearer) ---> |
|                 Loads SDK + circuit artifact           |
|                 User completes enrollment/auth         |
|                             |                          |
|                   4. SDK generates ZK proof (WASM)     |
|                      Submits via same-origin proxy     |
|                             | --- POST /v1/verify ---> |
|                             |  <-- { token: JWT }      |
|                             |                          |
|  5. Redirect to callback    |                          |
|  <-- callbackUrl?authResultCode=…                      |
|                             |                          |

Step 1: Create a session (server-side)

From your backend, create a session using your provider's secret key (sk_ prefix):

curl -X POST https://api.authensee.com/v1/sessions \
  -H "x-api-key: sk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "full",
    "externalUserId": "user_12345"
  }'

Parameters

FieldTypeRequiredDescription
scopestringYes"enroll", "authenticate", or "full" (enrollment + auth)
externalUserIdstringNoYour application's user ID (maps to an AuthenSee persona)
ttlnumberNoSession TTL in seconds (default: 3600, max: 86400)

Response

{
  "sessionId": "9182e25e-0b7b-4d92-ae98-a3c710c905c1",
  "sessionToken": "sess_aG25OA7mEnbOSX2J8UwnEE-GhoRdTLxN2hVeFxhWons",
  "flowCode": "flow_8s2k…",
  "hostedUrl": "https://auth.authensee.com/flow/flow_8s2k…",
  "scope": "full",
  "expiresAt": "2026-04-27T11:32:11.910Z"
}

The response carries a one-time flowCode and a ready-to-use hostedUrl (https://auth.authensee.com/flow/{flowCode}). Send the user to hostedUrl, not to the raw sessionToken — the token never travels in a URL.

Step 2: Send the user to the hosted URL

Redirect the browser to hostedUrl:

// Express.js example
app.get('/auth/start', async (req, res) => {
  const response = 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/auth/callback',
    }),
  });
 
  const session = await response.json();
  res.redirect(session.hostedUrl);
});

For a popup instead of a full redirect, hand hostedUrl to AuthenSee.open() from @rebellion-systems/authensee-embed — see the embed guide.

Step 3: User completes the flow

On the hosted page, the user sees a co-branded enrollment or authentication UI. The page:

  1. Redeems the one-time flowCode (POST /v1/hosted/flow-code/redeem) into an in-memory session token — there is no session cookie anywhere
  2. Validates the session with that token via GET /v1/sessions/current (Bearer, no provider secret key needed)
  3. Initializes the AuthenSee hosted runtime with the in-memory token; circuit artifacts are bundled with the hosted deployment — no runtime fetch
  4. Presents the enrollment or authentication flow based on session scope, rendered with <EnrollmentFlow> / <AuthenticationFlow> from @rebellion-systems/authensee-ui
  5. Generates ZK proofs in the browser via WASM (Barretenberg UltraHonk), forwarded upstream through a same-origin proxy that attaches the Authorization: Bearer header

Important: Questions are configured per provider (not stored on the auth server). The hosted page loads question configuration from the provider's settings.

The hosted flow holds the session token in memory (a root client provider) for the lifetime of the flow — never in a cookie and never in React state outside that store, and never in a URL. Because there is no cookie, the flow works in any top-level context, including a popup. The one-time flowCode is the only thing that travels in the URL, and it is single-use: a mid-flow full-page refresh restarts the flow (the code can't be redeemed twice).

The hosted flow also denies framing: it sends Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY, so the pages cannot be embedded in a hostile iframe (anti-clickjacking). This is why enrollment/recovery use a popup rather than an inline iframe.

The whole UI layer — enrollment flow, authentication flow, theme — lives in @rebellion-systems/authensee-ui and is consumed via the <AuthenSeeProvider> wrapper. If you self-host pages, you import the same package; nothing diverges from what the AuthenSee-managed pages run.

Step 4: Handle the callback

After the user completes the flow, they are redirected to your callback URL with a one-time result code, which your backend exchanges server-side with your secret key:

https://myapp.com/auth/callback?authResultCode=…&sessionId=9182e25e…

Callback parameters

ParameterDescription
authResultCodeOne-time auth result code — exchange it server-side with your secret key. Absent for enroll-only flows.
linkedPresent ("1") when an existing persona was linked rather than newly enrolled.
providerSubjectAuthenSee's provider-scoped stable alias for the persona, when applicable.
sessionIdThe session ID for correlation.

Handle the callback

app.get('/auth/callback', async (req, res) => {
  const { authResultCode } = req.query;
 
  if (authResultCode) {
    // Exchange the one-time result code server-side with your secret key.
    // The exchange returns the signed JWT (personaId, providerId, persona_type, …).
    const result = await exchangeAuthResultCode(authResultCode);
    req.session.userId = result.personaId;
    res.redirect('/dashboard');
  } else {
    res.redirect('/auth/error');
  }
});

When using the popup drop-in, this same callback page just calls relayCallback() from @rebellion-systems/authensee-embed — it relays the authResultCode back to your opener page over a BroadcastChannel instead of you handling a top-level redirect.

Branding

Hosted pages are co-branded: AuthenSee owns the frame and you contribute a small, consistent identity. Your provider configuration supplies:

  • Logo (URL) — falls back to your display name, then the AuthenSee wordmark
  • Display name (co-branded into the frame)
  • One brand color (drives both the primary action and the accent)
  • One copy line (tagline)
  • Light or dark surface

The frame keeps a constant "Secured by AuthenSee" mark. Configure this on the Brand identity page in the admin dashboard. The projection is delivered to the hosted pages as an AuthenSeeTheme on the session. See theming for the full co-brand model.

Local development

Test the full hosted pages flow locally:

# From the authnc monorepo root
export NODE_AUTH_TOKEN=$(gh auth token)
./scripts/dev-local.sh

This starts PostgreSQL + Auth Server (Docker) + Hosted Pages (Next.js dev server) and prints test URLs for enrollment and authentication.

Performance

MetricHosted pages (WASM)SDK (native, planned)
Proof generation3-5 seconds (desktop)1-3 seconds (mobile)
Integration time< 1 hour< 1 day
Frontend code requiredNoneYes

On this page