The drop-in component
A launcher, an SRI-pinned script, and two backend routes you still have to write.
What it is
<unifyid-continue> is a custom element published as @vn.unifyid/connect. It renders the Continue with UnifyID control and launches the flow.
It is a launcher, never a consent surface. Consent always renders on a UnifyID-owned page. There are zero iframes: the element opens a popup, falls back to a redirect when the popup is blocked, and deliberately uses redirect on mobile and narrow screens.
The postMessage traffic between the popup and your page carries completion and state only — never tokens. A browser message on its own never authorizes anything.
Loading it, pinned
Assets are SRI-pinned and immutable per version. Load the script with its integrity hash on both the initiating page and the registered callback page, before anything uses window.UnifyID. The script contains no client secret.
<script src="https://developers.staging.unifyid.io/sdk/unifyid-connect/v1.5.2/unifyid-connect.js"
integrity="sha384-R9C2QH49DGZzSpAWlx7+OlGJPFDqWRFcV6b8F2IN8upEhSWwicvh2lsagcWRYe10"
crossorigin="anonymous" defer></script>
<unifyid-continue
start-url="/api/auth/unifyid/start"
scopes="openid profile email"
mode="popup"></unifyid-continue>Allow the pinned asset origin in your CSP and preserve cross-origin CORS headers on the asset, or SRI will refuse it. Use same-origin-allow-popups on the initiating page's opener policy and do not sever the callback's opener.
The two routes you must provide
The component is not a drop-in *integration*. It is a drop-in *button*. The code exchange uses your client secret, so it happens on your server, in routes you write.
POST /api/auth/unifyid/start
receives{ scopes, purpose, display }.must do- Validate the origin. Enforce the scopes and purpose you configured rather than trusting what arrived. Generate
state,nonceand the PKCE pair server-side and bind them to an HTTP-only browser cookie. returns{ authorizationUrl, state, callbackOrigin }.statemust be at least 32 characters of cryptographic randomness and match the authorization URL exactly;callbackOriginmust equal the initiating application's origin.
POST /api/auth/unifyid/complete
receives{ state, code, error, errorDescription }.must do- Atomically consume the unexpired, browser-bound transaction. Exchange the code server-side. Validate issuer, audience, signature, nonce and expiry. Create the application session.
returns{ completed: true }only after the session exists. A denial returns{ completed: false, error: "access_denied" }. Other failures use a non-success status and carry no secrets.
await window.UnifyID.completeServerBoundCallback({
completionUrl: "/api/auth/unifyid/complete",
});
// Verify your own session endpoint before showing anything protected,
// then navigate to a fixed destination. This also finishes the redirect
// fallback, where there is no opener at all.
await fetch("/api/session");const button = document.querySelector("unifyid-continue");
button.addEventListener("unifyid:success", async () => {
// A browser event is not authorization. Ask your own server.
const session = await fetch("/api/session").then((r) => r.json());
if (session.signedIn) location.assign("/app");
});
button.addEventListener("unifyid:cancel", () => showRetry());
button.addEventListener("unifyid:error", (event) => showError(event.detail));A runnable reference lives at examples/hosted-server-bound/ in the developer portal repository. Its in-memory persistence is local-only and must be replaced before you deploy.
What this does not do
- Cross-host handoff is not implemented. A popup cannot create cookies on another tenant host. Start, callback and completion must be same-origin. If you need a cross-host handoff you are building and reviewing it yourself — and relaying access tokens or widening cookie domains is not the shortcut it looks like.
- Legacy `client-id` / `redirect-uri` attributes still exist for migration only. New web integrations use the server-bound flow above.
- Never deploy the localhost snippet. The production asset origin is an https origin your deployment operator confirms.
Verified against API commit 234dba22 on 2026-10-18. The routes, scopes, checklist, events and limits on these pages are read from that export, not typed.