LoginForm manages the entire authentication flow internally through a multi-step state machine. As a developer, your responsibility is wiring the output callbacks — routing the user after success, redirecting to TOTP setup when required, and navigating to signup or password recovery. You do not manage individual steps or render challenge UI unless you explicitly take over challenge rendering via onChallengeRequired.
Login State Machine
LoginForm uses an internal step state with five possible values: email, credential, challenge, totp, and totp-setup. The form advances through these steps based on the LoginResponse returned by POST /auth/login.
OTP_EMAIL mode, the credential step is skipped — the form calls the login API directly after email entry, and the backend responds with a challenge.
LoginResponse Branches
| Branch | Condition | What happens | Developer action |
|---|---|---|---|
| Success | accessToken present | Tokens set automatically, onSuccess(userId) fires | Navigate to dashboard |
| Challenge required | challengeRequired && challengeId | ChallengeView renders inline (or onChallengeRequired fires if provided) | Handle inline or via callback |
| TOTP required | requiresTotp && pendingToken | TOTP code input renders; verified via POST /auth/totp/verify | None — handled internally |
| TOTP setup required | requiresTotpSetup | onTotpSetupRequired() fires | Navigate user to TOTP setup screen |
LoginForm Props
Controls the visual layout of the form.
full-screen centers the form on a gray background with a card container. side-screen renders a split view with a branded color panel on the left and the form on the right. Defaults to 'full-screen'.Called after successful authentication — tokens are set and
getMe() has resolved. The userId string is derived from the authenticated user record.Optional. Called when the user clicks “Create account” in the email step. Only rendered when the merchant’s
signupMode is OPEN. Wire this to navigate to your signup page.Optional. Called when the user clicks “Forgot password?” in the credential step. Only rendered when the merchant’s
allowPasswordRecovery is enabled. Wire this to navigate to your password recovery page.Optional. Called when the backend response includes
requiresTotpSetup: true. The form enters a holding state — navigate the user to your TOTP setup page.Optional. Called when the backend responds with
challengeRequired: true. Receives the challengeId needed to render ChallengeView.If
onChallengeRequired is provided, LoginForm does not render the inline ChallengeView. You must render ChallengeView yourself with the challengeId passed to this callback.ChallengeView Dual-Response Contract
POST /challenges/:id/complete can return one of two response shapes depending on the challenge type:
LoginResponse— returned forLOGIN_OTPchallenges (passwordless email OTP login). ContainsaccessTokenandrefreshToken.ChallengeResponse— returned forFRICTION,SIGNUP, andEMAIL_CHANGEchallenges. Contains status only — no tokens.
ChallengeView exposes two separate callbacks to handle each case:
LoginForm, both callbacks are used. When the inline ChallengeView fires onLoginSuccess, it means the backend confirmed LOGIN_OTP and returned tokens directly. When it fires onComplete, it means a FRICTION challenge was resolved and the form re-calls handleLoginAttempt with the stored credentials to complete authentication.
ChallengeView Props
Required. The challenge ID returned by the login API when
challengeRequired is true. Pass it directly from LoginResponse.challengeId or from the onChallengeRequired callback.Called when a non-login challenge completes successfully. The result is a
ChallengeResponse — no tokens are present. Use this for FRICTION, SIGNUP, and EMAIL_CHANGE challenge types.Called when a
LOGIN_OTP challenge completes and the backend returns tokens directly. The result is a LoginResponse with accessToken and refreshToken already set in memory by useChallenge.Optional string to override the default challenge dialog title.
Optional string to override the default challenge subtitle / instruction text.
The OTP delivery method. Defaults to
'EMAIL'. Use 'TOTP' for authenticator-app challenges. LoginForm always passes 'EMAIL' for login challenges.OTP_EMAIL Login Mode
The
OTP_EMAIL login mode (passwordless email OTP) is not yet documented as stable. When loginMode is OTP_EMAIL, LoginForm skips the password step and immediately calls the login API with email only. The backend sends a challenge containing a one-time code. This flow is functional but pending staging verification — do not rely on it for production until confirmed.Putting It Together
A standard login page wires only the outcome callbacks.LoginForm handles challenge and TOTP steps internally:
LoginForm handles challenge and TOTP steps internally. You do not need to render ChallengeView yourself unless you provide onChallengeRequired to take over challenge rendering.onChallengeRequired and render ChallengeView yourself:
Next steps
Hooks: useAuth & useUser
Access the authenticated user’s identity and profile data after login.
Signup & Forgot Password
Account creation, email verification, and password recovery flows.

