Custom flows (headless)
The drop-in <SignUp> and
<ForgotPassword> cards handle the full flow
for you. When you need your own markup and copy, the
credential & flow hooks let you drive the exact
same flows. The network and parsing still run on the Torii runtime, so you don’t
reimplement (or pin) the security-critical logic: you only build the UI.
Everything here requires a <ToriiProvider>
ancestor.
Custom sign-up
Section titled “Custom sign-up”Sign-up is two steps: start() validates and emails a 6-digit code, then
verifyCode() confirms it and signs the user in. The session is applied
automatically on success.
import { useSignUp } from '@torii-js/torii-react';
function SignUp() { const { state, start, verifyCode, resend } = useSignUp();
if (state.status === 'needs-code' || state.status === 'code-invalid') { return ( <CodeForm invalid={state.status === 'code-invalid'} onSubmit={(code) => verifyCode(code)} onResend={resend} /> ); } if (state.status === 'code-expired') return <Expired onRestart={() => location.reload()} />;
return ( <DetailsForm busy={state.status === 'submitting'} error={state.status === 'error' ? state.error : null} onSubmit={(email, password) => start({ email, password, legalConsentAccepted: true })} /> );}Legal consent
Section titled “Legal consent”If the environment requires legal consent, useSignUp() surfaces what you need:
const { start, legalSettings } = useSignUp();// Render your consent checkbox from legalSettings, then:await start({ email, password, legalConsentAccepted: true });If you skip required consent, start() resolves to { status: 'error' } with
the reason in error.code.
Custom password reset
Section titled “Custom password reset”import { usePasswordReset } from '@torii-js/torii-react';
function ResetPassword() { const { state, request, confirm } = usePasswordReset();
if (state.status === 'idle' || state.status === 'requesting') { return <EmailForm busy={state.status === 'requesting'} onSubmit={(email) => request(email)} />; } if (state.status === 'done') return <Done />; // 'code-sent' | 'resetting' | 'code-invalid' | 'error' return ( <NewPasswordForm invalid={state.status === 'code-invalid'} onSubmit={(code, newPassword) => confirm({ code, newPassword })} /> );}request() always resolves to code-sent on success regardless of whether the
email exists, so your UI can’t be used to enumerate accounts.
Custom passwordless email-code sign-in
Section titled “Custom passwordless email-code sign-in”Available when the environment has Passwordless email code enabled (see
authentication configuration).
There is no dedicated hook: the two calls sit directly on useAuth(), because
each is a single request with no intermediate state to manage.
import { useAuth } from '@torii-js/torii-react';
function EmailCodeSignIn() { const { startEmailCodeSignIn, verifyEmailCodeSignIn, signIn } = useAuth(); const [sent, setSent] = useState(false);
async function request(email: string) { const outcome = await startEmailCodeSignIn({ email }); // 'sent' | 'rate-limited' | 'disabled' | 'error' | 'network-error' if (outcome.kind === 'sent') setSent(true); }
async function submit(email: string, code: string) { const outcome = await verifyEmailCodeSignIn({ email, code }); if (outcome.kind === 'success') signIn(outcome.tokens); if (outcome.kind === 'mfa-required') { // An emailed code is a FIRST factor. Complete the challenge exactly as // you would after a password sign-in — see the MFA guide. } }}Two contract details your UI has to respect:
sentsays nothing about whether the account exists. The server answers identically for a known and an unknown address so the endpoint cannot be used to enumerate your users, so there is no outcome to branch on. Show “if an account exists, we sent a code”.- Handle
mfa-required. Skipping that arm means an enrolled user’s sign-in silently stops after they enter a correct code.
Every rejection at the verify step — wrong code, expired, already used, too many
attempts — comes back as the single invalid-code outcome. That is deliberate:
the server does not distinguish them on the wire either.
Applying the session yourself
Section titled “Applying the session yourself”By default these hooks call useAuth().signIn(tokens) for you. To control it
(for example, “register but don’t log in yet”), opt out and apply the tokens
when you choose:
const { state, verifyCode } = useSignUp({ autoSignIn: false });const { signIn } = useAuth();
const next = await verifyCode(code);if (next.status === 'success') signIn(next.tokens);Next steps
Section titled “Next steps”- Credential & flow hooks reference: every hook, state, and option.