Hosted pages
The redirect-based integration and the full ceremony narrative — enrollment, authentication, policy upgrades, and persona linking with the QR handoff.
What you'll build: a login flow where your backend mints a session, the browser is redirected to an AuthenSee-hosted page for the ceremony, and your callback route completes the login server-side.
When to use this: when you prefer a full-page handoff over the popup embed — for example on mobile web, or in apps that already model auth as a redirect (OAuth-style). Both surfaces run the identical hosted flow; only the way the user enters and leaves it differs. This page also documents every ceremony the hosted flow can run — enrollment, authentication, policy upgrade, and persona linking — and exactly what your callback receives in each case.
How it works
Create a session on your backend
Pick the scope for the job: enroll for first-time setup, authenticate for a returning login, full when the hosted flow should decide (it checks the account's provider binding and branches accordingly). See POST /v1/sessions for every field, including enrollMode and providerSubject.
The response carries a single-use flowCode inside a ready-to-use hostedUrl (https://auth.authensee.com/flow/{flowCode}). Send the user to hostedUrl — the sessionToken in the same response is for server-side session-token calls only and must never reach a browser URL.
Send the user to the hosted URL
A plain redirect (as above) or the popup — the hosted flow behaves identically in both. On arrival, the page:
- Redeems the single-use
flowCodefor an in-memory session token — there is no session cookie anywhere - Validates the session via
GET /v1/sessions/current, which returns your co-brand theme, the session scope, and the ceremony descriptor for your current factor combination - Runs the right ceremony (below), generating ZK proofs in the browser via WASM — the network only ever sees the proof and its public inputs
- Redirects to your
callbackUrlwith the outcome
Because the token lives only in memory and only the single-use flowCode ever travels in a URL, a mid-flow full-page refresh restarts the flow — the code can't be redeemed twice. The pages also deny framing (frame-ancestors 'none'), which is why there is no iframe surface. See hosted flow surface security.
The user completes a ceremony
Which ceremony runs depends on the session scope, the account's state, and your current policy. Proof generation takes a few seconds in the browser; the flow shows progress throughout.
First-time enrollment
The user completes your policy's factors — registers a passkey, and picks memorable image points or performs a motion gesture, depending on your factor combination. Everything is hashed on-device; only a single aggregate commitment reaches the server (POST /v1/enrollments). The persona also opportunistically registers its passkey-only scheme in the same call, future-proofing it against a later policy switch.
Authentication
A returning user re-completes their factors. The flow binds a challenge, generates the proof on-device, and submits it (POST /v1/verify). A wrong answer fails locally inside the prover — no server round-trip, nothing learned by anyone. On success the server mints the one-time authResultCode your callback receives.
Policy upgrade
If you tightened your factor combination since the user enrolled, their login fails with 409 policy_upgrade_required — and the hosted flow turns that into a guided upgrade instead of a lockout. The user proves a factor they already have (commonly the universal passkey-only factor), that same proof authorizes adding the new on-policy scheme (POST /v1/enrollments/add), and the login then completes normally. Nothing is deleted; the persona gains an additional scheme.
Linking an existing persona
A user who already enrolled through another AuthenSee provider is offered "log in with your existing persona" instead of enrolling fresh factors. They prove any scheme their persona already holds, and the proof authorizes linking the persona to your provider (POST /v1/personas/:personaId/provider-links).
Because passkeys can't be enumerated across origins, this is a prompt, not a lookup — and when the current device doesn't hold the right passkey (say, the user is on a work desktop but their passkey is on their phone), the flow offers a QR handoff:
- The desktop mints a single-use handoff code and renders it as a QR code
- The phone scans it, redeems the code for its own device-scoped session token, and completes the proof there
- The desktop long-polls
GET /v1/sessions/resultand resolves the moment the phone's proof lands — it never sees the proof itself
The handoff is also used for ceremonies that need device capabilities the current device lacks — a motion-gesture policy on a desktop without an accelerometer, for example (GET /v1/sessions/current reports this in its ceremony descriptor).
Handle the callback
The user returns to your callbackUrl with query parameters that differ by ceremony:
| Ceremony outcome | Callback parameters |
|---|---|
| Authentication (login) | authResultCode=ar_...&sessionId=... |
| Enrollment (no login performed) | sessionId=... — no result code to exchange |
| Persona link | linked=1&providerSubject=sub_...&sessionId=... — no authResultCode |
| Parameter | Description |
|---|---|
authResultCode | One-time result code — exchange it server-side with your secret key. Absent for enroll-only flows and persona links. |
linked | Present ("1") when an existing persona was linked rather than newly enrolled |
providerSubject | Your provider-scoped stable alias for the persona, when applicable |
sessionId | The session ID, for correlation with the session you minted |
When using the popup, this same callback page just calls relayCallback() — it relays the parameters 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 — logo, display name, one brand color, one copy line, and a light or dark surface. The frame keeps a constant "Secured by AuthenSee" mark. Configure it on the Brand identity page of your dashboard; the projection is delivered to the hosted pages as a theme on the session. See theming.
Troubleshooting
POST /v1/sessions fails with VALIDATION_ERROR about the callback URL. The callbackUrl origin must exactly match an entry on your provider's allowed-callback-origins list (scheme, host, and port). An empty allowlist rejects every callback URL.
POST /v1/sessions with scope: "authenticate" fails with NOT_FOUND. The externalUserId you passed has no AuthenSee enrollment yet. Mint an enroll or full session for first-time users, or let full scope branch automatically.
POST /v1/sessions fails with CONFLICT ("setup is incomplete"). The account started enrolling but never finished. Send the user through an enroll or full flow to complete setup.
The user refreshed mid-flow and was sent back to the start. Expected: the flowCode is single-use and the session token lives only in memory, so a refresh can't resume. Mint a fresh session.
A returning user was asked to add a factor. You changed your factor combination; their login hit policy_upgrade_required and the flow ran the guided upgrade. This is a one-time step per user per policy change.
The exchange fails with VALIDATION_ERROR ("already consumed" / "expired"). Result codes are single-use and short-lived. If your callback handler can retry (queues, at-least-once delivery), deduplicate by sessionId before exchanging.
Next
- Embed guide — the popup form of this same flow
- Sessions reference — sessions, the result poll, and the handoff endpoints
- Security model — the cookie-free hosted runtime and framing denial