Capxul Docs
Guides

Build the auth flow

Drive email OTP, onboarding, readiness, routing, and sign-out from one identity state.

@capxul/sdk-react exposes one canonical identity state and one stable auth facade. Your application owns forms, copy, routes, and invocation IDs.

Before you start

Mount CapxulProvider with a working publishable key. Choose requirement="deployed" if the signed-in journey must claim a deployed personal account.

Read the journey

import { useCapxulAuth, useCapxulDestination, useCapxulIdentity } from "@capxul/sdk-react";

function AuthState() {
  const identity = useCapxulIdentity();
  const destination = useCapxulDestination();
  const auth = useCapxulAuth();

  if (identity.phase === "signed_out") {
    return <EmailForm onSubmit={(email) => auth.requestCode(email)} />;
  }
  if (identity.phase === "otp_pending") {
    return <OtpForm onSubmit={(otp) => auth.verifyCode(otp)} />;
  }
  if (
    identity.phase === "otp_sending" ||
    identity.phase === "otp_verifying" ||
    identity.phase === "signing_out"
  ) {
    return <Pending />;
  }
  if (identity.phase === "faulted") {
    return <Failure code={identity.failure.code} />;
  }
  return <SignedIn destination={destination} onSignOut={() => auth.signOut()} />;
}

Every expected facade failure resolves { ok: false, reason }. It does not reject. Pass caller-owned controls as the optional final argument:

await auth.verifyCode(otp, {
  correlationId: crypto.randomUUID(),
  journeyId,
  timeoutMs: 15_000,
  signal,
});

Use app-owned slots

CapxulAuthenticationController implements the same exhaustive phase selection without rendering SDK markup:

<CapxulAuthenticationController
  slots={{
    email: EmailSlot,
    otp: OtpSlot,
    pending: PendingSlot,
    failure: FailureSlot,
    success: SuccessSlot,
  }}
/>

Complete onboarding

For a personal journey call auth.completePersonal(profileDetails, options). For the current Organization journey, use CapxulOnboardingController. It preserves Profile → Organization submit → account claim → Organization creation while your app retains the controlled form and submitted-request carrier.

<CapxulOnboardingController
  intent={intent}
  organizationProfile={organizationProfile}
  submittedOrganization={submittedOrganization}
  onIntent={setIntent}
  onOrganizationProfile={setOrganizationProfile}
  onSubmittedOrganization={setSubmittedOrganization}
  navigation={navigation}
  slots={slots}
/>

Retry actions receive fresh app-owned correlationId values while retaining the journey ID. The controller never creates either identifier.

Route and sign out

useCapxulDestination() returns a descriptor or null; map it to your own routes. Successful auth.signOut() clears the authenticated query boundary. The identity state then becomes signed_out.

On this page