Embedded integration
UnifyID component
Place a Continue with UnifyID control inside any web application while retaining the Hosted UnifyID security boundary.
Versioned release1. Load and configure
<script
src="https://developer.dev.unifyid.io/sdk/unifyid-connect/v1.2.1/unifyid-connect.js"
integrity="{sha384_from_release_manifest}"
crossorigin="anonymous"
defer>
</script>
<unifyid-continue
client-id="vid_sandbox_..."
redirect-uri="http://localhost:4000/callback"
scopes="openid profile email identity_verified"
purpose="Create and secure your account"
authorize-url="https://api.dev.unifyid.io/v1/oauth/authorize"
mode="popup">
</unifyid-continue>Pin the complete versioned URL in Production and copy its SHA-384 value from /sdk/unifyid-connect/manifest.json. The latest channel is intended only for evaluation because it can change without an application deployment.
The component opens a centered desktop window immediately from the person's click, then navigates it to Hosted UnifyID. It generates state, nonce, and S256 PKCE values, uses redirect mode on mobile, and falls back to redirect when a popup is unavailable. The Client ID is public; never place a Client Secret in browser code.
Component attributes
| Attribute | Required | Default | Contract |
|---|---|---|---|
client-id | Yes | None | Environment-specific public Client ID from the application. |
redirect-uri | Yes | None | Exact registered HTTP localhost or HTTPS callback URI. |
scopes | No | openid | Space-separated scope names enabled for the application. |
purpose | No | Application sign-in purpose | Plain-language consent purpose displayed by Hosted UnifyID; maximum 200 characters. |
authorize-url | Yes, unless globally configured | None | HTTPS UnifyID endpoint ending in /v1/oauth/authorize. Localhost HTTP is allowed locally. |
mode | No | popup | popup or redirect. Popup automatically becomes redirect on mobile and narrow screens. |
label | No | Continue with UnifyID | Visible accessible button label. Set localized text appropriate to the page language. |
disabled | No | Absent | Boolean attribute that prevents authorization and disables the internal button. |
Global configuration and telemetry
UnifyID.configure accepts an optional default authorizeUrl and an optional synchronous onEvent(event) observer. It returns the active URL and SDK version. Events are observational and contain no authorization code, token, verifier, claim, or personal information.
window.addEventListener("DOMContentLoaded", () => {
UnifyID.configure({
authorizeUrl: "https://api.dev.unifyid.io/v1/oauth/authorize",
onEvent(event) {
// { name, version, timestamp, mode? }
sendBoundedMetric(event);
}
});
});With a static defer script, configure inside DOMContentLoaded or from a later deferred script. When loading dynamically, call configure from the script element's load event. Do not call it before the SDK has created window.UnifyID.
sdk.configuredauthorization.startedauthorization.createdauthorization.redirectedauthorization.completedauthorization.cancelledauthorization.failedauthorization.timeoutauthorization.started precedes transaction creation. Exactly one terminal event should follow a popup attempt: completed, cancelled, failed, or timeout. Redirect navigation ends the current page lifecycle after authorization.redirected, so terminal completion is observed by the callback application rather than the original document.
2. Complete and exchange inside the popup
try {
await UnifyID.completePopupCallback({
exchangeUrl: "/api/auth/unifyid/exchange"
});
// The backend creates the session before the popup closes.
} catch {
// Show a bounded retry message inside the popup.
}3. Continue in the original page
const control = document.querySelector("unifyid-continue");
control.addEventListener("unifyid:success", ({ detail }) => {
if (!detail.completed) return;
window.location.assign("/dashboard");
});
control.addEventListener("unifyid:error", ({ detail }) => {
// Show a recoverable error without logging tokens.
});
control.addEventListener("unifyid:cancel", () => {
// Keep the person on the current page.
});Custom events
All component events bubble and are composed, so listeners attached to the custom element or an ancestor can receive them outside the Shadow DOM.
| Event | Detail | Meaning |
|---|---|---|
unifyid:success | { completed: true, state } | Recommended secure-popup flow. The callback backend exchanged the code and created the application session before signaling completion. |
unifyid:success | { completed: false, code, state, nonce, codeVerifier, redirectUri } | Advanced code-transport flow. Send these values immediately to the same-origin backend and never log or persist them in browser storage. |
unifyid:error | { code, message, error } | Configuration, popup, timeout, callback, or exchange failure. error remains as a compatibility alias for message. |
unifyid:cancel | { reason: "user_closed", message } | The person closed or cancelled the popup. Keep the current page usable and allow another attempt. |
The recommended flow produces completed: true. The explicit completed: false variant exists only for applications that intentionally receive the authorization code in the opener. It is not an intermediate loading state.
Framework integration
React and Next.js
Render the custom element from a Client Component and attach typed custom-event listeners through a ref. This works consistently across React versions and avoids treating custom event names as React props. Do not read window.UnifyID during server rendering.
"use client";
import { useEffect, useRef } from "react";
import "@unifyid/connect";
export function ContinueWithUnifyID() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
const success = (event: Event) => {
const detail = (event as CustomEvent).detail;
if (detail.completed) window.location.assign("/dashboard");
};
element?.addEventListener("unifyid:success", success);
return () => element?.removeEventListener("unifyid:success", success);
}, []);
return <unifyid-continue ref={ref} client-id="..." redirect-uri="..." />;
}For Next.js, place the import and element in a file containing "use client". The element's light-DOM markup is stable during SSR; registration and Shadow DOM creation happen only in the browser, preventing server access to Custom Elements APIs.
Vue
// vite.config.ts
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag === "unifyid-continue"
}
}
})
// Component template
<unifyid-continue
client-id="..."
redirect-uri="..."
@unifyid:success="handleSuccess" />Vue attributes are strings. Use disabled as a boolean attribute and listen to the native custom events rather than wrapping the component's internal button.
Styling and accessibility
The component uses an open Shadow DOM. Style its internal button with the exposed button CSS part. Internal markup is not a public styling contract.
unifyid-continue::part(button) {
min-height: 48px;
border-radius: 6px;
font: 600 14px Montserrat, system-ui, sans-serif;
}The internal control is a native button, supports keyboard activation, exposes disabled and aria-busy, and restores focus to itself after popup completion, cancellation, timeout, or failure. Use a descriptive localized label. Do not remove the visible focus indicator in application CSS. Hosted sign-in, consent, and face capture own their own focus management and language.
Content Security Policy
Content-Security-Policy:
default-src 'self';
script-src 'self' https://developer.dev.unifyid.io;
connect-src 'self' https://api.dev.unifyid.io;
base-uri 'none';
frame-ancestors 'none';
object-src 'none';The component uses a top-level popup or redirect and is not embedded in a frame. If your browser enforces navigate-to, allow the UnifyID authorization origin. The versioned script uses crossorigin="anonymous" for integrity checking; the UnifyID asset response therefore includes Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin. Keep token exchange on your same-origin backend.
Browser support
| Browser | Support policy |
|---|---|
| Chrome and Edge | Latest two stable desktop releases; current Android Chrome. |
| Firefox | Latest two stable desktop releases. |
| Safari | Latest two stable macOS releases; iOS and iPadOS Safari 16.4 or later. |
Required platform features are Custom Elements, Shadow DOM, Web Crypto, sessionStorage, URL, postMessage, and popup support. Embedded webviews are not supported unless they provide those APIs and preserve secure system-browser authorization behavior.
npm and TypeScript
The release contains the publishable @unifyid/connect package with global element, event, telemetry, configuration, and result declarations. Importing it registers the component.
npm install @unifyid/connect@1.2.1
import "@unifyid/connect";
import type {
UnifyIDAuthorizationResult,
UnifyIDErrorDetail,
UnifyIDTelemetryEvent
} from "@unifyid/connect";Until the package is visible in your configured npm registry, use the versioned script distribution. Do not replace a pinned release with an unversioned repository file.
Component boundary
The component never receives passwords, biometric images, provider payloads, face scores, access tokens, or confidential client secrets. The parent validates the callback origin, popup window, state, and message type. In the recommended flow, authorization codes and PKCE verifiers go directly from the callback to the same-origin application backend and are not sent through postMessage.
Backend exchange
Your exchange endpoint exchanges the code, validates the issued tokens, creates an HTTP-only application session, and returns a bounded success response. Then retrieve UserInfo as described in the Quickstart.
Operational release controls
Use an explicit component version and its matching integrity hash in every Production deployment.
Capture lifecycle events through UnifyID.configure({ onEvent }). Never attach tokens, authorization codes, claims, or personal information.
Track authorization starts, completions, cancellations, failures, and timeouts without recording identity data.
Retain the previously approved version so rollback requires only the earlier script URL and integrity value.
Run popup and redirect journeys across every supported browser before promoting a component release.