Web SDK API Reference
Complete API reference for Hawcx Web SDK
Factory
createHawcxAuth(config)
Creates a Hawcx client instance.
import { createHawcxAuth } from '@hawcx/core';
const client = createHawcxAuth({
configId: 'your_config_id',
apiBase: '<Base URL from Admin Console>/v1'
});Config (AuthConfig):
| Property | Type | Required | Description |
|---|---|---|---|
configId | string | Yes | Your Hawcx Config ID |
apiBase | string | No | Your tenant API URL (default https://api.hawcx.com/v1) |
logger | Logger | No | Logger for debugging (default silent) |
fetch | typeof fetch | No | Custom fetch implementation (for testing) |
timeout | number | No | Request timeout in ms (default 10000) |
Returns: HawcxAuth
Logging
Logger is an interface with optional debug / info / warn / error
methods, each (message: string, data?: unknown) => void. Two ready-made
instances are exported: noopLogger (silent, the default) and consoleLogger
(forwards to the browser console).
import { createHawcxAuth, consoleLogger } from '@hawcx/core';
const client = createHawcxAuth({ configId: '...', logger: consoleLogger });A StorageAdapter interface (getItem / setItem / removeItem, same shape
as localStorage) is also exported for typing custom storage backends.
State Machine
The client maintains an AuthState that drives your UI. Subscribe to changes:
client.onStateChange((state) => {
// Render UI based on state.status
});
// Or get current state
const state = client.getState();Flow States
AuthState is a union of five variants, each exported as its own type:
| Status | Type | Shape | Description |
|---|---|---|---|
idle | AuthStateIdle | { status: 'idle' } | Ready to start |
loading | AuthStateLoading | { status: 'loading', session?, notice? } | Processing request |
step | AuthStateStep | { status: 'step', session, step: AuthStep, notice? } | Server needs user input |
completed | AuthStateCompleted | { status: 'completed', session, authCode, expiresAt, sessionToken? } | Auth successful |
error | AuthStateError | { status: 'error', session?, error: AuthError, previousStep? } | Something failed |
previousStep on the error state preserves the step the user was on for
retryable errors (e.g. a wrong TOTP code), so your form can stay in place.
Completed State & AuthResult
interface AuthStateCompleted {
status: 'completed';
session: string;
authCode: string; // Send to backend
expiresAt: string; // ISO timestamp
sessionToken?: string; // hx_auth session JWT (bearer for self-service device calls), if minted
}The PKCE codeVerifier is not on the state — the SDK holds it locally from
flow start. Read the full completion via client.getCompletion() (or
useSession() in React), which returns an AuthResult:
interface AuthResult {
authCode: string; // Send to backend
expiresAt: string; // ISO timestamp
codeVerifier?: string; // Send to backend (PKCE)
sessionToken?: string; // hx_auth session JWT — bearer for logoutEverywhere() / self-service device calls
}sessionToken (added in 2.6.0) is the hx_auth session JWT, present only when the
server minted one. Pass it as the bearer to
logoutEverywhere() (or React's
logoutAllDevices) for single-logout / self-service device
management.
codeVerifier is optional because it lives only in the client instance that
called start(). Read it with client.getCompletion() as soon as the flow
completes and send it to your backend alongside authCode — a different client
instance (for example one created after a full page navigation) will not have
it, and token exchange fails without it.
AuthError
interface AuthError {
code: string; // Error code
message: string; // Human-readable message
category: AuthErrorCategory; // Recovery hint
details?: Record<string, unknown>;
}
enum AuthErrorCategory {
RETRYABLE = 'retryable',
USER_ACTION = 'user_action',
FATAL = 'fatal',
}AuthErrorCategory is a runtime enum — compare with
AuthErrorCategory.RETRYABLE etc., or against the string values shown.
| Category | Meaning | Recovery |
|---|---|---|
retryable | Transient error (network, rate limit) | Retry the same action |
user_action | User error (wrong code, invalid input) | Show error, let user retry |
fatal | Unrecoverable (session expired) | Call reset() and start over |
State Helpers & Type Guards
@hawcx/core also exports standalone helper functions for narrowing an
AuthState outside React (in React, useAuthFlags() covers
the common cases):
| Helper | Returns |
|---|---|
isIdle(state) / isLoading(state) / isAuthStep(state) / isCompleted(state) / isError(state) | boolean — narrows state to the matching variant |
getStepType(state) | The current step's type string, or null if not in a step |
getMethods(state) | The select_method step's methods array, or [] |
getAuthError(state) | AuthError | null |
getAuthNotice(state) | AuthNotice | null — the server-driven notice on the current step (see AuthNotice) |
Per-step type guards narrow state.step once you know state.status === 'step':
isSelectMethodStep, isEnterCodeStep, isEnterTotpStep, isSetupTotpStep,
isSetupSmsStep, isDeviceChallengeStep, isRedirectStep,
isAwaitApprovalStep, isCompletedStep, isErrorStep.
import { isAuthStep, isEnterCodeStep } from '@hawcx/core';
if (isAuthStep(state) && isEnterCodeStep(state.step)) {
console.log(state.step.destination); // fully typed
}AuthNotice (device revocation)
A notice is a non-fatal, server-driven message that rides along with the
current step. Unlike AuthError, it does not end the flow.
interface AuthNotice {
code: string; // e.g. 'unauthorized_device'
message: string; // Human-readable, safe to display
}When an admin revokes a device in the Admin Console, that device's next sign-in
returns 403 unauthorized_device. The SDK auto-recovers: it clears the
revoked credential and replays the flow once, skipping device trust so the user
re-verifies (OTP) and the device re-enrolls. No error is thrown — instead a
notice rides along on the verification step:
{
"code": "unauthorized_device",
"message": "This device's access was revoked. Verify your identity to continue."
}notice is declared only on the loading and step states, so narrow the state
before reading it:
const notice =
state.status === 'step' || state.status === 'loading'
? state.notice ?? null
: null;Render the notice as a banner above your form; the user re-verifies once and
continues. The drop-in HawcxSignUpSignIn component (see
React Components) does this automatically.
Steps
When state.status === 'step', the server is asking for user input. Each step
type below is exported as its own interface, with a matching
type guard. AuthStep is the union of all steps
and StepType is the union of their type strings — note both unions also
include one SDK-internal member, setup_device, whose interface and guard are
not individually exported (see the device_challenge section below).
select_method (SelectMethodStep)
User chooses an authentication method.
interface SelectMethodStep {
type: 'select_method';
phase: 'primary' | 'mfa' | 'enrollment'; // Which phase of the flow
methods: Method[];
}
interface Method {
name: string; // Method ID to pass to selectMethod()
label: string; // Display label
icon?: string; // Icon identifier
}Action: Call selectMethod(method.name)
enter_code (EnterCodeStep)
User enters an OTP code sent via email or SMS.
interface EnterCodeStep {
type: 'enter_code';
destination: string; // Masked email/phone, e.g. "s***@example.com"
channel?: 'sms' | 'email';
codeLength: number; // Expected code length
codeFormat: 'numeric' | 'alphanumeric'; // For keyboard hints
codeExpiresAt: string; // ISO 8601
resendAt: string; // When resend becomes available (ISO 8601)
}Action: Call submitCode(code)
enter_totp (EnterTotpStep)
User enters a code from their authenticator app.
interface EnterTotpStep {
type: 'enter_totp';
}Action: Call submitTotp(code)
setup_totp (SetupTotpStep)
User sets up TOTP for the first time.
interface SetupTotpStep {
type: 'setup_totp';
secret: string; // Base32 manual entry key
otpauthUrl: string; // otpauth:// URL for QR code
period: number; // TOTP period in seconds (typically 30)
backupCodes?: string[]; // One-time recovery codes — show once
}UI: Show QR code from otpauthUrl, display secret for manual entry. If
backupCodes is present, display them once and prompt the user to save them.
Action: Call submitTotp(code) after user enters code from authenticator app.
setup_sms (SetupSmsStep)
User enrolls their phone number.
interface SetupSmsStep {
type: 'setup_sms';
existingPhone?: string; // Masked existing phone being replaced, if any
}Action: Call submitPhone(phoneNumber)
await_approval (AwaitApprovalStep)
Waiting for external approval (QR scan, push notification).
interface AwaitApprovalStep {
type: 'await_approval';
qrData?: string; // QR code data (if applicable)
expiresAt: string; // When approval expires (ISO 8601)
pollInterval: number; // Suggested poll interval in seconds
}UI: Show QR code if qrData provided, otherwise show "Waiting for approval..."
Action: None. The SDK polls automatically and transitions when approved.
redirect (RedirectStep)
Redirect to external OAuth provider. This is the SDK-embedded pattern's handoff when Social/SSO is enabled — it is not the OIDC /oauth2/token flow. See Choose your integration pattern to reconcile the three redirect flows.
interface RedirectStep {
type: 'redirect';
url: string; // Full redirect URL
returnScheme?: string; // Deep link scheme for mobile
nonce?: string; // Token mode (One Tap): session-bound OIDC nonce
clientId?: string; // Token mode: upstream IdP client_id
}Action: window.location.href = step.url
device_challenge (DeviceChallengeStep) — SDK-internal
Device trust verification. Handled automatically by the SDK — never render
it. When the server recognizes an enrolled device it returns this step; the
SDK loads the device key from secure storage, signs the challenge, and submits
without user interaction. It is exported (with isDeviceChallengeStep) so
custom step-handling code can recognize and skip it.
interface DeviceChallengeStep {
type: 'device_challenge';
challenge: string; // Challenge bytes to sign
challengeEncoding: 'base64';
keyId: string; // Key ID to sign with
algorithm: 'Ed25519';
domain: string; // Domain for signature binding
combinedsalt: string; // Base64 salt for key unwrap
responseEncoding: 'base64';
}A sibling setup_device step (device enrollment) is likewise SDK-internal and
handled automatically. Unlike the steps above, its interface (SetupDeviceStep)
and guard are not exported from the package — if you write an exhaustive
switch over StepType, handle 'setup_device' with a pass-through (the SDK
answers it before your UI ever sees it in practice).
completed (CompletedStep) and error (ErrorStep)
These two close out the flow. The SDK consumes them and surfaces
state.status === 'completed' / 'error' instead, so most apps never handle
them as steps — the types matter when working with client.send() or
previousStep directly.
interface CompletedStep {
type: 'completed';
authCode: string; // Authorization code
expiresAt: string; // ISO 8601
}
interface ErrorStep {
type: 'error';
code: string; // Machine-readable code (for logging, not branching)
// Server-driven recovery action — BRANCH ON THIS. The open `(string & {})`
// union means newer servers may send actions not in ErrorAction.
action: ErrorAction | (string & {});
message: string; // Human-readable message
retryable: boolean;
details?: ErrorDetails;
}
type ErrorAction =
| 'retry_input' // Wrong input, let user try again
| 'restart_flow' // Session invalid, start over
| 'wait' // Rate limited — wait details.retry_after_seconds
| 'retry_request' // Transient failure — safe to retry the same action
| 'abort' // Unrecoverable, show error
| 'resend_code' // Code expired, offer resend
| 'select_method'; // Go back to method selection
interface ErrorDetails {
retry_after_seconds?: number; // For 'wait'
errors?: Array<{ field: string; message: string }>; // Validation failures
[key: string]: unknown;
}Branch on action, not code — unknown actions from newer servers should
fall back to showing message.
Wire Protocol Types
For advanced use (custom transports, request logging, client.send()), the
raw protocol v2 types are exported:
| Export | Description |
|---|---|
PROTOCOL_VERSION | The protocol version constant (2) |
WireRequest | Client → server envelope: { protocolVersion, session?, action } |
WireResponse | Server → client envelope: { protocolVersion, session, step, meta } |
ResponseMeta | { traceId, expiresAt } — tracing id and session expiry |
Action | Union of all client actions (below) |
AuthStep / StepType | Union of all steps / of their type strings |
DeviceInfo | Device fingerprint payload sent with start: { v: 1, fingerprint, signals? } |
AuthMode | 'signin' | 'signup' | 'account_manage' |
Action types — one interface per action, discriminated by type:
StartAction (start), SelectMethodAction (select_method),
SubmitCodeAction (submit_code), SubmitTotpAction (submit_totp),
SubmitPhoneAction (submit_phone), SubmitSignatureAction
(submit_signature, device trust — SDK-internal), OAuthCallbackAction
(oauth_callback, social login return leg), ResendAction (resend),
PollAction (poll), CancelAction (cancel). Each client method documented
under Methods sends the corresponding action.
The Action union has one more member: request_challenge
(device-enrollment keyset upload, sent automatically by the SDK after a
setup_device step). Like SetupDeviceStep, its interface is SDK-internal
and not individually exported — an exhaustive switch over action.type in a
custom transport or request logger must still expect it.
Methods
start(identifier, flowType?)
Begin an authentication flow.
client.start('user@example.com');| Parameter | Type | Description |
|---|---|---|
identifier | string | User email |
flowType | AuthMode — 'signin' | 'signup' | 'account_manage' | Optional flow override |
If flowType is omitted, the SDK defaults to 'signin'. Whether an unknown
user is then moved into a signup flow is decided server-side by tenant
policy (unknown_user), not by the SDK — on tenants configured to reject
unknown users, a plain start(email) for a new user ends in an error state.
selectMethod(methodId)
Select an authentication method after select_method step.
client.selectMethod('email_otp');| Parameter | Type | Description |
|---|---|---|
methodId | string | Method name from step |
submitCode(code)
Submit OTP code after enter_code step.
client.submitCode('123456');| Parameter | Type | Description |
|---|---|---|
code | string | OTP code |
submitTotp(code)
Submit TOTP code after enter_totp or setup_totp step.
client.submitTotp('123456');| Parameter | Type | Description |
|---|---|---|
code | string | Authenticator code |
submitPhone(phoneNumber)
Submit phone number after setup_sms step.
client.submitPhone('+15551234567');| Parameter | Type | Description |
|---|---|---|
phoneNumber | string | Phone number (E.164 format) |
resend()
Resend OTP code. Valid after enter_code step.
client.resend();reset()
Clear state and return to idle. Use to start over or recover from fatal errors.
client.reset();cancel()
Cancel the current flow.
client.cancel();getState()
Get the current flow state.
const state = client.getState();onStateChange(callback)
Subscribe to state changes.
const unsubscribe = client.onStateChange((state) => {
console.log('State changed:', state.status);
});
// Later: unsubscribe();send(action)
Send a raw protocol action to the server. This is the
primitive every convenience method above wraps — use it for actions that have
no wrapper, such as the oauth_callback return leg of social login.
const state = await client.send({ type: 'oauth_callback', code, state: csrfState });getCompletion()
Get the completion result (AuthResult | null). Returns the result only while
the client is in the completed state; see
Completed State & AuthResult.
const result = client.getCompletion();
if (result) sendToBackend(result.authCode, result.codeVerifier);onCompletion(callback)
Subscribe to completion changes. Fires with an AuthResult when a flow
completes, and with null when the session is cleared (e.g. signOut()) —
always handle the null case.
const unsubscribe = client.onCompletion((result) => {
if (result) onSignedIn(result);
else onSignedOut();
});signOut()
Reset to idle and clear the completion (notifies onCompletion listeners with
null).
client.signOut();logoutEverywhere(bearerToken)
End all active sessions for the user (single-logout across every device), not
just this one. Added in 2.6.0. Pass the sessionToken from the completed
AuthResult as the bearer.
const result = client.getCompletion();
if (result?.sessionToken) {
const { sessionsEnded } = await client.logoutEverywhere(result.sessionToken);
console.log(`ended ${sessionsEnded} sessions`);
}Returns: Promise<{ sessionsEnded: number }>. In React, use
useAuthActions().logoutAllDevices, which wraps this.
getSessionId() / getConfigId() / hasDeviceCredentials(identifier)
| Method | Returns | Description |
|---|---|---|
getSessionId() | string | undefined | Current flow session id (for multi-step flows) |
getConfigId() | string | The configured Config ID |
hasDeviceCredentials(identifier) | Promise<boolean> | Whether this browser has stored device-trust credentials for the identifier |
resumeSession(session, identifier?) / getPkceVerifier()
Carry a flow across a full page navigation (e.g. a social-login redirect). Before
redirecting, persist getSessionId() and getPkceVerifier(); after returning,
adopt the flow on the fresh client instance with resumeSession(...).
// before redirecting:
sessionStorage.setItem('hx', JSON.stringify({
session: client.getSessionId(),
verifier: client.getPkceVerifier(), // string | null — persist so token exchange still works
}));
// after returning, on a new client instance:
const { session, verifier } = JSON.parse(sessionStorage.getItem('hx')!);
client.resumeSession(session); // resumeSession(session: string, identifier?: string): void| Method | Signature | Notes |
|---|---|---|
resumeSession(session, identifier?) | (session: string, identifier?: string) => void | Re-attach a fresh client to an in-progress flow. Pass identifier when the resumed leg may enroll a device. |
getPkceVerifier() | () => string | null | The PKCE verifier for the current flow. Persist it before a redirect — a resumed client instance won't have it, and token exchange fails without it. |
Utilities
getDeviceInfo()
Read basic device information from the browser. Exported from @hawcx/core.
import { getDeviceInfo } from '@hawcx/core';
const { bui, osType } = getDeviceInfo();Returns:
| Property | Type | Description |
|---|---|---|
bui | string | Browser user information (the user agent string) |
osType | string | Operating system, from navigator.userAgentData.platform with navigator.platform fallback |
Both fields fall back to 'unknown' outside a browser environment.
Note: this return shape is not the
DeviceInfowire type ({ v, fingerprint, signals }) that appears in protocol payloads — that type describes what the SDK sends to the server;getDeviceInfo()is a small client-side convenience.
React Components
Prebuilt components from @hawcx/react. All of them must be rendered inside a
HawcxProvider. For the prebuilt styling, import the
stylesheet once:
import '@hawcx/react/dist/styles.css';<HawcxSignUpSignIn />
The drop-in authentication component — the fastest path to a working integration. It renders the entire flow for you: identifier form, method selection, OTP / TOTP entry, TOTP and SMS enrollment (including QR codes), QR / push approval waiting, risk verification, and error states. You only handle the result.
import { HawcxProvider, HawcxSignUpSignIn } from '@hawcx/react';
import '@hawcx/react/dist/styles.css';
function LoginPage() {
return (
<HawcxProvider config={{ configId: '...', apiBase: '...' }}>
<HawcxSignUpSignIn
onSuccess={({ authCode, codeVerifier }) => {
// Send both to your backend for code exchange
}}
/>
</HawcxProvider>
);
}Props (HawcxSignUpSignInProps):
| Prop | Type | Required | Description |
|---|---|---|---|
onSuccess | (result: AuthResult) => void | No | Called when authentication completes. result contains authCode and codeVerifier to send to your backend |
className | string | No | Extra class(es) appended to the root hawcx-auth-container element |
Known issue (through SDK 2.6.0): the component forwards completion notifications to
onSuccessunguarded, so callingclient.signOut()elsewhere while it is mounted invokesonSuccess(null). Guard against a nullresultif your app can sign out with the component on screen.
The component ships with sensible built-in behavior: resend cooldown (30 s), device-revocation notice banners, and a risk dialog when the server flags a risky sign-in. Use it when you want a working login screen now; drop down to the form hooks when you need your own markup.
<RiskDetectedDialog />
The verification prompt shown when the server flags a sign-in as risky (new
device, unusual location, impossible travel, …). HawcxSignUpSignIn renders
it automatically; it is exported for custom UIs that want the same screen.
Internal risk-reason codes are mapped to user-friendly text before display.
The risk payload rides on the current step as an untyped wire field — no exported step interface declares it, so read it with a cast (the SDK's own component does the same):
import { RiskDetectedDialog } from '@hawcx/react';
// `risk` is not part of the exported AuthStep types:
const risk = state.status === 'step'
? (state.step as any).risk as
| { detected?: boolean; reasons?: string[];
location?: { city: string | null; country: string | null };
message?: string }
| undefined
: undefined;
{risk?.detected && (
<RiskDetectedDialog
reasons={risk.reasons ?? []}
location={risk.location ?? { city: null, country: null }}
message={risk.message ?? 'For your security, we need to verify your identity.'}
onNext={() => proceedWithVerification()}
onCancel={() => cancelFlow()}
/>
)}Props (RiskDetectedDialogProps):
| Prop | Type | Required | Description |
|---|---|---|---|
reasons | string[] | Yes | Risk reason codes from the step's risk payload |
location | { city: string | null; country: string | null } | Yes | Detected sign-in location, rendered as "City, Country". If both fields are null the location row is omitted entirely |
message | string | Yes | Explanatory message shown to the user |
onNext | () => void | Yes | User chose to continue with verification |
onCancel | () => void | Yes | User dismissed — typically cancel the flow |
<QrCode />
Renders a string as a QR code image. Used internally for setup_totp
(otpauthUrl) and await_approval (qrData) steps; exported for custom UIs.
import { QrCode } from '@hawcx/react';
<QrCode value={step.otpauthUrl} pixelSize={220} />Props:
| Prop | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Content to encode |
pixelSize | number | No | Rendered width/height in px (default 220) |
data-testid | string | No | Test id on the image (default hawcx-qr) |
Shows a "Loading..." placeholder of the same size while the image is generated.
React Hooks
HawcxProvider
Wrap your app to provide the client context.
import { HawcxProvider } from '@hawcx/react';
<HawcxProvider config={{ configId: '...', apiBase: '...' }}>
<App />
</HawcxProvider>Instead of config, you can pass an existing instance via client (e.g. one
created with createHawcxAuth). At least one of the two must be provided (the
provider throws if both are missing); if both are given, client takes
precedence and config is ignored.
useAuthState()
Get the current flow state. Re-renders on state changes.
const state = useAuthState();useAuthFlags()
Derived boolean flags for the current state (returns AuthFlags). Saves you
from matching on status yourself, and surfaces any server-driven notice.
const {
isIdle, isLoading, isStep, isCompleted, isError,
notice, // AuthNotice | null — non-fatal message on the current step
deviceRevoked, // convenience: true when notice.code === 'unauthorized_device'
} = useAuthFlags();notice saves you from narrowing the state yourself, and deviceRevoked is the
convenience flag for the revoked-device case (see
AuthNotice). Render it as a banner above your
form — the SDK auto-recovers, so the user just re-verifies once:
const { notice, deviceRevoked } = useAuthFlags();
return (
<>
{notice && <Banner tone={deviceRevoked ? 'warn' : 'info'}>{notice.message}</Banner>}
<SignInForm />
</>
);useAuthActions()
Get action methods. Each wraps the corresponding client method.
const {
send, // raw protocol action (e.g. oauth_callback)
start,
selectMethod,
submitCode,
submitTotp,
submitPhone,
resend,
reset,
cancel,
signOut,
logoutAllDevices, // (bearerToken) => Promise<{ sessionsEnded: number }>
} = useAuthActions();logoutAllDevices(bearerToken) ends every active session for the user
(single-logout) and returns how many were ended — wraps the client's
logoutEverywhere. Pass the sessionToken from
the completed AuthResult as the bearer.
useAuthClient()
Get the underlying client instance.
const client = useAuthClient();useSession()
Access the completion result after authentication finishes. Unlike
useAuthState(), which tracks the step-by-step flow, useSession() answers
"has this client completed auth?" and re-renders on completion changes
(including signOut()).
const { session, actions } = useSession();
if (session) {
// session is an AuthResult: authCode, codeVerifier, expiresAt
return <button onClick={actions.signOut}>Sign out</button>;
}Returns:
| Property | Type | Description |
|---|---|---|
session | AuthResult | null | Completion result of this client instance, or null |
isLoading | boolean | Reserved — currently always false (there is no async hydration phase) |
error | AuthError | undefined | Reserved — currently never set |
actions.signOut | () => void | Clear the session on this client |
Scope: the session is in-memory state of the current client instance — it does not survive a page reload, and there is no cross-tab persistence (the SDK writes no session storage for other tabs to observe). To keep users signed in across reloads, exchange the
AuthResultwith your backend and use your own application session.
Form Hooks
The form hooks wrap useAuthActions() with ready-made input state: value,
setter, trimming, empty-input guard, and an isSubmitting flag. Use them when
building a custom UI so you don't rewire the same form plumbing; use
useAuthActions() directly when you need full control. HawcxSignUpSignIn
is built from these same hooks.
useIdentifierForm(options)
State for the identifier (email) input that starts the flow.
const { identifier, setIdentifier, isSubmitting, submit } =
useIdentifierForm({ flowType: 'signin' });
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
<input value={identifier} onChange={(e) => setIdentifier(e.target.value)} />
<button disabled={isSubmitting}>Continue</button>
</form>Options:
| Property | Type | Required | Description |
|---|---|---|---|
flowType | 'signin' | 'signup' | 'account_manage' | Yes | Passed to start() |
Returns: { identifier, setIdentifier, isSubmitting, submit }. submit()
trims the identifier, ignores empty input, and calls start(identifier, flowType).
useOtpForm()
State for the code input on enter_code, enter_totp, and setup_totp
steps. It detects whether the current step wants an OTP or a TOTP code
(including after a retryable TOTP error) and routes submit() to
submitCode() or submitTotp() accordingly — one form handles both.
const { code, setCode, submit, resend, destination, isTotp, isSubmitting } =
useOtpForm();
<p>{isTotp ? 'Enter your authenticator code' : `Code sent to ${destination}`}</p>Returns:
| Property | Type | Description |
|---|---|---|
code | string | Current input value |
setCode | (code: string) => void | Update the input |
submit | () => Promise<void> | Submit as OTP or TOTP based on the current step. Clears the input once the submission resolves — including on a wrong code, since SDK actions resolve to an error state rather than throw |
resend | () => Promise<void> | Resend the OTP (valid on enter_code) |
destination | string | null | Masked email/phone from the enter_code step |
isTotp | boolean | True when the current step expects an authenticator code |
isSubmitting | boolean | True while a submit is in flight |
usePhoneForm()
State for the phone number input on the setup_sms step.
const { phone, setPhone, submit, isSubmitting } = usePhoneForm();Returns: { phone, setPhone, submit, isSubmitting }. submit() trims the
value, ignores empty input, calls submitPhone(phone) (E.164 format), and
clears the input once the submission resolves (on error states too — SDK
actions resolve rather than throw).
useMethodSelection(state)
Extract the available methods from a select_method step.
import type { AuthStateStep } from '@hawcx/react';
function MethodPicker({ state }: { state: AuthStateStep }) {
const { methods } = useMethodSelection(state);
// methods: Method[] — empty if the current step is not select_method
return methods.map((m) => <MethodButton key={m.name} method={m} />);
}| Parameter | Type | Description |
|---|---|---|
state | AuthStateStep | The current step state |
Returns: { methods: Method[], session: string } — memoized; methods is
[] when the current step is not select_method.
useMethodDisplayName(method)
Display name for a method: its label, falling back to name. Memoized.
const displayName = useMethodDisplayName(method);Backend API
HawcxOAuth
Exchange authorization codes for verified user claims.
import { HawcxOAuth } from '@hawcx/oauth-client';
// Discovery mode (recommended): enforces iss + aud. Construct once at startup.
const oauth = await HawcxOAuth.fromIssuer({
issuer: process.env.HAWCX_BASE_URL!, // your environment's Base URL
configId: process.env.HAWCX_CONFIG_ID!, // sent as the X-Config-Id header
clientId: process.env.HAWCX_CLIENT_ID!, // expected `aud` on id_tokens
});fromIssuer config:
| Property | Type | Required | Description |
|---|---|---|---|
issuer | string | Yes | Your environment's Base URL; discovery target and expected iss |
configId | string | Yes | Config ID — sent as the X-Config-Id header |
clientId | string | Yes | Client ID — enforced as the aud claim on id_tokens |
timeout | number | No | Per-request timeout (ms) for token exchange + JWKS fetch (default 10000) |
discoveryTimeout | number | No | Timeout (ms) for the one-time discovery fetch (default 5000) |
clockToleranceSeconds | number | No | Tolerated clock skew (s) when verifying exp / nbf (default 10) |
For 4.x back-compat the legacy single-arg constructor new HawcxOAuth({ configId, baseUrl }) (signature-only — no iss/aud enforcement) is still supported; see the Node.js API reference for its options.
from hawcx_oauth_client import HawcxOAuth
import os
oauth = HawcxOAuth.from_issuer(
os.environ['HAWCX_BASE_URL'], # issuer URL (Base URL from Admin Console)
os.environ['HAWCX_CONFIG_ID'],
os.environ['HAWCX_CLIENT_ID'], # expected `aud` on issued id_tokens
)exchangeCode(authCode, codeVerifier)
Exchange authorization code for verified claims.
const { idToken, claims } = await oauth.exchangeCode(authCode, codeVerifier);
// claims.sub = user ID
// claims.email = verified emailresult = oauth.exchange_code(auth_code, code_verifier)
# result.claims['sub'] = user ID
# result.claims['email'] = verified emailReturns:
| Property | Type | Description |
|---|---|---|
idToken | string | Raw JWT (don't use as access token) |
claims | object | Verified claims (sub, email, etc.) |
Error Types
import { TokenExchangeError, TokenVerificationError } from '@hawcx/oauth-client';
try {
const { claims } = await oauth.exchangeCode(authCode, codeVerifier);
} catch (error) {
if (error instanceof TokenExchangeError) {
// Code exchange failed (invalid/expired code)
console.error(error.message, error.statusCode);
}
if (error instanceof TokenVerificationError) {
// JWT verification failed
console.error(error.message);
}
}| Error | When | Recovery |
|---|---|---|
TokenExchangeError | Code invalid, expired, or already used | Ask user to re-authenticate |
TokenVerificationError | JWT signature/claims failed | Log incident, re-authenticate |
Flow Types
Most apps can omit the flow type — start(email) defaults to 'signin', and
tenants configured with unknown_user='create' transition new users into
signup server-side. On stricter tenant policies, pass 'signup' explicitly
for registration.
| Type | When to Use |
|---|---|
signin | Returning user login |
signup | New user registration |
account_manage | Step-up auth for sensitive actions |
Last updated on