@mohitebiz/white-labeled-auth
v1.0.16
Published
White-labeled authentication & onboarding SDK. Works across React, Angular, Vue and Vanilla JS.
Downloads
1,118
Maintainers
Readme
@black-ink-technologies/white-labeled-auth
Framework-agnostic SDK for ChainIT Hosted Auth — a fully managed, branded onboarding flow that covers OTP, face liveness, and ID capture in ChainIT-hosted screens. Drop the SDK into any React / Vue / Angular / vanilla-JS app, point it at your backend's client-session proxy, and receive standard OIDC tokens via callback.
Full product documentation: https://develop-sdk.chainit.online/docs/applications/hosted-auth
How it fits together
Browser (your app) Your backend ChainIT API
───────────────── ──────────── ───────────
HostedAuthProvider ────► GET /api/hosted-auth/ ──► POST /users/v1/hosted-auth/client-session
config.session.getSession client-session (clientId + clientSecret, server-side)
◄──── ◄── { sessionId, sessionSignature }
◄──── sanitized session
SDK renders hosted UI ──────────────────────────────► Hosted Auth flow
◄────────────────────────────── accessToken, idToken, refreshToken
callbacks.onSuccess(tokens)clientSecret never reaches the browser — your backend proxies the client-session call and returns only { sessionId, sessionSignature }.
Prerequisites
- A Hosted Auth application created in the Developer Portal (or via
POST /dev-portal/v1/oauth-appswith"type": "HOSTED_AUTH"). clientId/clientSecretstored as server-side only environment variables.- A backend route on your side that proxies
POST /users/v1/hosted-auth/client-sessionand returns the sanitized session payload ({ sessionId, sessionSignature }).
Installation
npm install @black-ink-technologies/white-labeled-authPeer requirements: react >= 18, axios, @tanstack/react-query, react-hook-form, jwt-decode, uuid, and zod (optional, used by request/response validation).
React usage
import {
HostedAuthProvider,
InitiateAuthFlow,
ReverifyFace,
type HostedAuthConfiguration,
} from "@black-ink-technologies/white-labeled-auth";
const config: HostedAuthConfiguration = {
clientId: "<<your_client_id>>",
session: {
getSession: async () => {
const res = await fetch("/api/hosted-auth/client-session", {
method: "POST",
});
if (!res.ok) throw new Error("Session fetch failed");
return res.json();
},
},
callbacks: {
onSuccess: (tokens) => {
logger.log("Auth success:", tokens);
},
onError: (error) => {
logger.error("Auth error:", error);
},
},
config: {
face: { timeout: 30_000 },
},
};
export function App() {
return (
<HostedAuthProvider config={config}>
<InitiateAuthFlow initialScreen="signin" />
{/* Or render only standalone re-verification when the user is already signed in */}
{/* <ReverifyFace onSuccess={() => {}} onClose={() => {}} /> */}
</HostedAuthProvider>
);
}HostedAuthProvider loads app branding and theme once, then shares it across all hosted-auth children.
Mobile QR face handoff
When the desktop browser cannot use a camera during face liveness, the SDK can show a QR code. Scanning that QR on a phone opens the same integrator page that started the auth flow, with a one-time handoff code in the URL (?h=<code>).
No extra route or consumer wiring is required. HostedAuthProvider automatically detects the handoff code and renders the mobile face-scan handoff surface instead of the app children, so this works whether the phone browser is logged in or logged out. The phone page only displays the face scan; it does not continue the normal login/profile flow.
Handoff query param. The code is carried on a query param named h by default. If h collides with something your app already uses, rename it with handoffParam on HostedAuthConfiguration:
const config: HostedAuthConfiguration = {
clientId: "<<your_client_id>>",
session: { getSession },
handoffParam: "cv", // QR becomes https://your-app.com/login?cv=<code>
};The same value is sent to the backend when minting the QR, so the QR, the URL, and the SDK's detection all stay in sync. It must be a URL-safe key (letters, digits, _, -; max 32 chars); anything else falls back to h.
⚠️ Reserve this param. The SDK reads (and on success deletes)
?h=<code>— or your customhandoffParam— on every page wrapped byHostedAuthProvider. Do not reuse the handoff param for any other purpose in your app, or a normal page load could be misread as a face handoff.
Security and cleanup:
- The face scan runs in the embedded-host secondary face iframe.
- The iframe sends the integrator origin as
X-Origin; the backend validates it against the Hosted Auth app's allowlist before resolving the handoff code. - Any stale Hosted Auth request token on the integrator origin is cleared before the iframe loads.
- The resolved request token and QR id are kept in iframe memory only and cleared on completion/cancel.
- On success, the SDK removes the handoff param from the phone URL; the backend also burns the handoff code.
If you need to handle the handoff yourself, pass disableFaceHandoffAutoMount to HostedAuthProvider and mount FaceHandoff on your own route. Most integrations should leave the default auto-mount behavior enabled.
Plain JS / UMD usage
<div id="hosted-auth-root"></div>
<script src="https://unpkg.com/@black-ink-technologies/white-labeled-auth@latest/dist/sdk/chainit-auth.umd.js"></script>
<script>
async function getSession() {
const res = await fetch("/api/hosted-auth/client-session", {
method: "POST",
});
if (!res.ok) throw new Error("Session fetch failed");
return res.json();
}
const hosted = window.HostedAuth.configure({
clientId: "<<your_client_id>>",
session: { getSession },
callbacks: {
onSuccess: (tokens) => console.log("Auth success:", tokens),
onError: (error) => console.error("Auth error:", error),
},
config: { face: { timeout: 30000 } },
});
hosted.initiateAuthFlow("#hosted-auth-root", { screen: "signin" });
// Logout from anywhere (e.g. a separate Sign-out button)
// const result = await hosted.logout();
// if (!result.success) console.warn("Server logout failed:", result.message);
// Tear down the host element (SPA navigation, mobile WebView dismiss)
// hosted.destroy("#hosted-auth-root");
</script>configure() eagerly initialises the SDK's HTTP layer and token storage, so hosted.logout() (or window.HostedAuth.logout()) is callable anywhere on the page — including settings screens or sign-out buttons that live outside #hosted-auth-root. Both hosted and window.HostedAuth expose the same imperative API.
The UMD bundle exposes the SDK as window.HostedAuth (with window.ChainItAuth kept as a backward-compatible alias).
Configuration
interface HostedAuthConfiguration {
clientId: string;
session: {
getSession: () => Promise<{ sessionId: string; sessionSignature: string }>;
};
callbacks?: {
onSuccess?: (tokens: {
accessToken: string;
idToken?: string;
refreshToken?: string;
}) => void;
onError?: (error: unknown) => void;
};
config?: {
face?: {
timeout?: number;
};
};
handoffParam?: string;
}| Field | Type | Required | Notes |
| --------------------- | -------- | :------: | ------------------------------------------------------------------------------------------------------------------------------------ |
| clientId | string | ✅ | OAuth client ID for the application. Sent on every hosted-auth + branding request. |
| session.getSession | function | ✅ | Returns { sessionId, sessionSignature } from your backend's /client-session proxy. |
| callbacks.onSuccess | function | – | Receives { accessToken, idToken?, refreshToken? } once onboarding completes. |
| callbacks.onError | function | – | Receives fatal SDK errors that the built-in retry UI could not recover from. |
| config.face.timeout | number | – | Liveness detection timeout in milliseconds. Defaults to 30000. |
| handoffParam | string | – | Query-param name for the mobile QR face-handoff code. URL-safe key (max 32 chars); defaults to h. Reserve it for the handoff only. |
Component reference
HostedAuthProvider
| Prop | Type | Required | Description |
| ----------------------------- | ------------------------- | :------: | ------------------------------------------------------------------------------------------------------ |
| config | HostedAuthConfiguration | ✅ | Session resolver + callbacks + optional feature flags. |
| children | ReactNode | ✅ | The hosted-auth surface (InitiateAuthFlow / ReverifyFace). |
| disableFaceHandoffAutoMount | boolean | – | Opt out of automatic ?h=<code> mobile QR handoff handling. Defaults to false (auto-mount enabled). |
InitiateAuthFlow
Drives the full onboarding journey: sign-in / sign-up → OTP → consents → face liveness → ID scan → token issuance.
| Prop | Type | Default | Description |
| --------------- | -------------------------------- | ------------------------------------------ | ------------------------------------- |
| initialScreen | 'signin' \| 'signup' \| 'face' | server-resolved (falls back to 'signin') | Deep-link to a specific entry screen. |
ReverifyFace
Step-up face liveness for users who are already signed in. Use for high-value or sensitive actions (payments, account changes) or periodic re-proofing. Requires a stored access token (tokenStorage.setTokens) and HostedAuthProvider in the tree.
| Prop | Type | Required | Description |
| ----------- | ------------------------------------ | :------: | ----------------------------------------------------------------------------------------------------------------- |
| onSuccess | () => void \| Promise<void> | – | Fires when face liveness is confirmed and the user is re-verified. |
| onError | (error: AuthError \| null) => void | – | Fires on a fatal re-verification failure. The SDK shows a built-in retry UI before bubbling unrecoverable errors. |
| onClose | () => void | – | Fires when the user dismisses the camera UI or completes verification. |
FaceHandoff
Advanced mobile QR handoff surface. Normally you do not mount this component yourself because HostedAuthProvider auto-mounts it when the page is opened with ?h=<code>. It is exported for integrations that opt out with disableFaceHandoffAutoMount and want to route the handoff manually.
| Prop | Type | Required | Description |
| ----------- | ------------------------------------ | :------: | ------------------------------------------------------------------------------------------------------ |
| onSuccess | () => void | – | Fires after the phone-side face verification succeeds. The waiting desktop is notified by the backend. |
| onError | (error: AuthError \| null) => void | – | Fires on terminal phone-side handoff errors or cancellation. |
Imperative JS API (HostedAuth)
HostedAuth.configure(options) returns an instance with three methods:
| Method | Signature | Purpose |
| ------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| initiateAuthFlow | (container?, options?: { screen?: 'signin' \| 'signup' \| 'face' }) => void | Mount the full onboarding flow. container is a CSS selector or HTMLElement; omit to use the SDK's default root. |
| reverifyFace | (container?) => void | Mount the standalone face re-verification overlay. |
| destroy | (container?) => void | Unmount and clean up. Omitting container also resets internal SDK state. |
| logout | (options?: LogoutOptions) => Promise<LogoutResult> | Revoke the active session on the server (/oauth/logout) and clear local tokens. See Logout below. |
The same calls are also available as static methods on HostedAuth for backward compatibility (HostedAuth.initiateAuthFlow(...), HostedAuth.render(...), HostedAuth.destroy(...), HostedAuth.logout(...)).
User profile (UserInfo API)
Fetch the authenticated user's profile from /oauth/userInfo. The SDK's axios interceptor automatically attaches the stored access token — no token parameter needed.
React
import { useUserInfo } from "@black-ink-technologies/white-labeled-auth";
function Profile() {
const { data: userInfo, isLoading, error, refetch } = useUserInfo();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <p>Hello, {userInfo?.name ?? userInfo?.email}</p>;
}Imperative JS / UMD
import {
HostedAuth,
fetchUserInfo,
} from "@black-ink-technologies/white-labeled-auth";
// Option A — static method (requires configure() first)
const userInfo = await HostedAuth.fetchUserInfo();
// Option B — standalone function (same constraint)
const userInfo = await fetchUserInfo();| Field | Type | Description |
| :----------------------- | :-------- | :----------------------------- |
| name | string | Full display name |
| email | string | Email address |
| picture | string | Profile picture URL |
| preferred_username | string | Preferred username |
| user_id / id / sub | string | Unique user identifier |
| phone_number | string | Phone number |
| email_verified | boolean | Whether the email is confirmed |
| phone_number_verified | boolean | Whether the phone is confirmed |
| updated_at | string | ISO timestamp of last update |
Token storage helpers
After onSuccess fires, the SDK persists tokens in browser storage. Apps that need to react to token changes can use the exported helpers:
import {
tokenStorage,
getStoredAccessTokenSnapshot,
subscribeStoredAccessToken,
STORED_TOKENS_CHANGED_EVENT,
type StoredTokens,
} from "@black-ink-technologies/white-labeled-auth";
const current = getStoredAccessTokenSnapshot();
const unsubscribe = subscribeStoredAccessToken(
(tokens: StoredTokens | null) => {
// tokens === null when the user signs out / tokens are cleared.
},
);STORED_TOKENS_CHANGED_EVENT is the underlying DOM event name if you need to subscribe outside the helper (e.g. from a non-React layer).
Logout
logout() revokes the active refresh-token chain on POST /oauth/logout, blacklists the stored access token via Authorization: Bearer, and clears local token storage. Local storage is always cleared — even if the server call fails — so the user is signed out locally regardless of network outcome.
type LogoutOptions = {
refreshToken?: string; // defaults to tokenStorage.getRefreshToken()
idTokenHint?: string; // defaults to tokenStorage.getIdToken()
postLogoutRedirectUri?: string;
};
type LogoutResult = { success: boolean; message: string };React
import { useLogout } from "@black-ink-technologies/white-labeled-auth";
function SignOutButton() {
const logout = useLogout();
const handleClick = async () => {
const result = await logout();
if (!result.success) {
console.warn(
"Server logout failed; tokens cleared locally:",
result.message,
);
}
};
return <button onClick={handleClick}>Sign out</button>;
}Imperative JS / UMD
import { HostedAuth } from "@black-ink-technologies/white-labeled-auth";
HostedAuth.configure({
clientId: "<<your_client_id>>",
session: { getSession },
});
await HostedAuth.logout();performHostedAuthLogout is also exported for non-React callers that prefer a plain function.
Cross-framework navigation
Imperative helpers safe to call from React, Vue, Angular, vanilla JS, or axios interceptors:
import {
navigate,
HOSTED_AUTH_NAVIGATE_EVENT,
OnboardingScreen,
} from "@black-ink-technologies/white-labeled-auth";
navigate(OnboardingScreen.VerifyOtp);See useOnboardingNavigation JSDoc in the source for the full contract.
Backend endpoints used by the SDK
| Endpoint | Method | Purpose |
| ------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------- |
| /users/v1/hosted-auth/client-session | POST | Server-to-server session bootstrap (your backend calls this; the browser does not). |
| /users/v1/hosted-auth/app-config | POST | Load app configuration after session bootstrap. |
| /users/v1/hosted-auth/branding | GET | Load tenant branding (logo, colors, screen copy). |
| /users/v1/hosted-auth/initiate | POST | Start the OTP challenge. |
| /users/v1/hosted-auth/resend-otp | POST | Resend the OTP for the active session. |
| /users/v1/hosted-auth/verify | POST | Verify the OTP. |
| /users/v1/hosted-auth/consents | POST | Capture terms / privacy acceptance. |
| /users/v1/hosted-auth/face/session | GET | Start face liveness session. |
| /users/v1/hosted-auth/face/verify | POST | Verify face liveness. |
| /users/v1/hosted-auth/face/reverification | GET / POST | Standalone face re-verification (used by ReverifyFace). |
| /users/v1/hosted-auth/liveness/qr-session | POST | Mint a QR handoff when the desktop cannot use a camera. |
| /users/v1/hosted-auth/liveness/qr-session/resolve | GET | Resolve a scanned one-time handoff code on the phone; validates X-Origin. |
| /users/v1/hosted-auth/liveness/qr-session/status | GET | Poll QR handoff status while the desktop waits. |
| /users/v1/hosted-auth/idscan/verify | POST | Verify ID document scan. |
| /users/v1/hosted-auth/init-user-vdt-and-device-vdt-v2 | POST | Issue final OIDC tokens. |
| /oauth/logout | POST | Revoke the active session (used by useLogout / HostedAuth.logout). |
| /oauth/userInfo | GET | Fetch the authenticated user's profile (used by fetchUserInfo / useUserInfo). |
All requests carry the rotating Authorization: Bearer <requestToken> issued by client-session; the browser never sees clientSecret.
Handling the response
The onSuccess callback receives standard OIDC tokens:
accessToken— pass to the SDK's built-infetchUserInfo()to fetch the user's profile, or call the ChainIT UserInfo API directly.idToken— identity claims. Validate via JWKS.refreshToken— opaque ({jti}.{secret}); send to your backend's refresh proxy to mint a newaccessToken/idToken.
Error handling
The SDK ships a built-in error surface for every step:
- Session-bootstrap and identity-verification failures render an inline error UI with a Retry button so the user can re-attempt the failed step without restarting the whole journey.
- After the user-visible retry path is exhausted, errors bubble to
callbacks.onError. ReverifyFacemirrors the same behaviour — built-in retry first, thenonError.
Branding
Branding (logo, primary / secondary colors, brand name, font, per-screen copy) is loaded automatically from the hosted-auth branding endpoint based on the app behind your client-session. Manage it via the Developer Portal Branding tab or the /dev-portal/v1/branding API — see Branding customization.
The SDK applies the colors as Tailwind / CSS theme tokens at runtime, so no frontend changes are required when the brand updates.
Public API (named exports)
| Export | Type |
| --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| HostedAuthProvider, InitiateAuthFlow, ReverifyFace, FaceHandoff | React components |
| HostedAuthProviderProps, InitiateAuthFlowProps, ReverifyFaceProps, FaceHandoffProps | React component prop types |
| HostedAuth, ChainItAuth | Imperative JS / UMD API (and its instance type HostedAuthInstance) |
| HostedAuthConfiguration, HostedAuthSessionConfig, HostedAuthCallbacksConfig, HostedAuthFeatureConfig, HostedAuthOptions | Config types |
| tokenStorage, getStoredAccessTokenSnapshot, subscribeStoredAccessToken, STORED_TOKENS_CHANGED_EVENT, StoredTokens | Token-storage helpers |
| useLogout, performHostedAuthLogout, LogoutOptions, LogoutResult | Server-backed logout (revokes refresh token, blacklists access token, clears local storage) |
| navigate, HOSTED_AUTH_NAVIGATE_EVENT, HostedAuthNavigateOptions, OnboardingScreen | Cross-framework navigation helpers |
| fetchUserInfo, useUserInfo, UserInfo | User profile API — fetch user data from /oauth/userInfo |
Internal modules are intentionally not exported to keep the SDK surface small.
Legacy compatibility
The previous OnboardingProvider entry point is preserved for in-flight integrations and now lives in OnboardingProvider.tsx (renamed from App.tsx). New integrations should use HostedAuthProvider + InitiateAuthFlow to get the latest features (branding context, error retry UI, opaque refresh tokens, resend-otp endpoint, etc.).
Development
This package is built as a library (no local Vite app). Use the standard scripts:
npm run lint
npm run typecheck
npm run build # builds lib + sdk + types
npm run preview