useSignIn() hook

useSignIn() is the stateful sign-in hook behind <SignInForm />, OtherSignInOptions, and the rest of the embedded sign-in flow. It creates sign-in attempts, keeps the latest in-progress attempt in memory, and exposes the continuation methods that move that attempt through verification, profile completion, SSO, or passkey sign-in.

Usage

The following example shows a basic usage of useSignIn().

export default function PasswordSignIn() {  const { loading, signIn, signinAttempt } = useSignIn();  async function submit() {    if (loading) {      return;    }    await signIn.createStrategy('email')({      email: 'jane@example.com',      password: 'correct horse battery staple',    });  }  return (    <div>      <button onClick={submit}>Sign in</button>      {signinAttempt ? <p>Current step: {signinAttempt.current_step}</p> : null}    </div>  );}

Return value

The hook returns the following fields and methods.

loading?: boolean | undefined;
Stays `true` until `useClient()` is ready. While loading is true, the hook does not expose a usable `signIn` object.
signIn?: { createStrategy; prepareVerification; completeVerification; completeProfile; identify; initEnterpriseSso } | undefined;
The stateful sign-in controller. This is where the embedded sign-in form gets its strategy builders and its continuation methods.
createStrategy?: (strategy: 'username' | 'email' | 'phone' | 'email_otp' | 'magic_link' | 'oauth' | 'passkey' | 'generic') => Function | undefined;
Returns the function for a specific sign-in strategy. The returned function posts the first sign-in request and stores the latest sign-in attempt when the response includes one.
username?: ({ username: string; password: string }) => Promise<ApiResult<Session>> | undefined;
Starts a username/password sign-in.
email?: ({ email: string; password: string }) => Promise<ApiResult<Session>> | undefined;
Starts an email/password sign-in.
phone?: ({ phone: string }) => Promise<ApiResult<Session>> | undefined;
Starts a phone OTP sign-in.
email_otp?: ({ email: string }) => Promise<ApiResult<Session>> | undefined;
Starts an email OTP sign-in.
magic_link?: ({ email: string }) => Promise<ApiResult<Session>> | undefined;
Starts a magic-link sign-in.
oauth?: ({ provider: OAuthProvider; redirectUri?: string }) => Promise<ApiResult<{ oauth_url: string; session: Session }>> | undefined;
Starts a social OAuth sign-in. When the request succeeds, the hook immediately assigns `window.location.href` to the returned provider URL.
passkey?: () => Promise<ApiResult<Session>> | undefined;
Starts a passkey sign-in. The hook requests browser WebAuthn credentials, converts the browser response, and completes the sign-in.
generic?: ({ email?: string; username?: string; password?: string; phone?: string; strategy?: string }) => Promise<ApiResult<Session>> | undefined;
The mixed strategy used by `SignInForm`. It lets the form submit identifier-first flows without committing to one builder up front.
prepareVerification?: (params: { strategy: 'email_otp'; redirectUri?: string } | { strategy: 'phone_otp'; lastDigits?: string } | { strategy: 'magic_link'; redirectUri?: string }) => Promise<ApiResult<{ otp_sent?: boolean; masked_phone?: string; masked_email?: string; verification_method?: string }>> | undefined;
Prepares the next verification step for the current sign-in attempt. This is what the embedded flow uses before OTP and magic-link verification screens.
completeVerification?: (verificationCode: string) => Promise<Session> | undefined;
Submits a verification code for the current attempt and updates `signinAttempt` with the latest server state.
completeProfile?: (data: ProfileCompletionData) => Promise<Session> | undefined;
Finishes the profile-completion step when the sign-in flow requires missing user fields before finalizing the session.
first_name?: string | undefined | undefined;
First name collected during profile completion.
last_name?: string | undefined | undefined;
Last name collected during profile completion.
username?: string | undefined | undefined;
Username collected during profile completion.
phone_number?: string | undefined | undefined;
Phone number collected during profile completion.
phone_country_code?: string | undefined | undefined;
Phone country code sent with the phone number when the flow collects one.
email?: string | undefined | undefined;
Email collected during profile completion.
identify?: (identifier: string) => Promise<{ strategy: "sso" | "social" | "password"; connection_id?: string; idp_url?: string; provider?: string }> | undefined;
Looks up the identifier-first result for an entered email or username. `SignInForm` uses this to decide whether to show password entry, enterprise SSO, or a social handoff.
initEnterpriseSso?: (connectionId: string, redirectUri?: string) => Promise<{ sso_url: string; session: Session }> | undefined;
Starts an enterprise SSO redirect for a known connection.
signinAttempt?: SigninAttempt | null | undefined;
The latest in-progress sign-in attempt captured by the hook. Embedded auth screens use this to decide which step to render next.
id?: string | undefined;
Stable identifier for the current attempt.
email?: string | undefined;
Email associated with the attempt when one is available.
method?: 'plain' | 'sso' | 'passkey' | undefined;
High-level sign-in method behind the current attempt.
sso_provider?: 'x_oauth' | 'github_oauth' | 'gitlab_oauth' | 'google_oauth' | 'facebook_oauth' | 'microsoft_oauth' | 'linkedin_oauth' | 'discord_oauth' | undefined;
Social or SSO provider attached to the attempt when relevant.
current_step?: 'verify_password' | 'verify_email' | 'verify_email_link' | 'verify_email_otp' | 'verify_phone' | 'verify_phone_otp' | 'verify_second_factor' | 'add_second_factor' | 'complete_profile' | undefined;
Current step in the sign-in state machine.
first_method_authenticated?: boolean | undefined;
Whether the first factor has already been completed.
second_method_authenticated?: boolean | undefined;
Whether the second factor has already been completed.
second_method_authentication_required?: boolean | undefined;
Whether this attempt still requires a second factor.
available_2fa_methods?: string[] | undefined | undefined;
Available second-factor methods when the attempt is waiting for a 2FA choice.
completed?: boolean | undefined;
Whether the attempt has fully finished.
requires_completion?: boolean | undefined | undefined;
Whether profile completion is still required.
required_fields?: string[] | undefined | undefined;
Fields the flow requires before the attempt can complete.
missing_fields?: string[] | undefined | undefined;
Fields still missing from the attempt.
profile_completion_data?: ProfileCompletionData | undefined | undefined;
Partial profile data already collected during the sign-in flow.
first_name?: string | undefined | undefined;
Collected first name.
last_name?: string | undefined | undefined;
Collected last name.
username?: string | undefined | undefined;
Collected username.
phone_number?: string | undefined | undefined;
Collected phone number.
phone_country_code?: string | undefined | undefined;
Collected phone country code.
email?: string | undefined | undefined;
Collected email.
discardSignInAttempt?: () => void | undefined;
Clears the in-memory sign-in attempt and returns the hook to its initial local state.
setSignInAttempt?: (attempt: SigninAttempt | null) => void | undefined;
Lets higher-level components restore or replace the current attempt explicitly. `SignInForm` uses this when it resumes a stored or returned attempt.

How it works

The hook keeps signinAttempt in local React state. Every builder except OAuth and passkey updates that state when the server returns session.signin_attempts.
That local attempt is what lets the embedded sign-in UI move from the initial credential screen into OTP, magic-link, second-factor, or profile-completion screens without rebuilding the whole flow from scratch.
OAuth behaves differently from the other strategies. Its builder requests an OAuth init payload and immediately redirects the browser to the returned provider URL.
Passkey also behaves differently. It runs a full browser WebAuthn flow, converting challenge and credential payloads between base64url strings and browser buffer types before finishing the sign-in.
The hook itself does not choose which screen to render next. It exposes the current attempt state, and higher-level components such as <SignInForm /> and TwoFactorVerification decide which UI branch to show.
Identifier-first flows are split between identify() and the actual strategy builders. identify() only tells you which path to take. The real attempt is still created by one of the sign-in strategies afterward.

When to use it

    Examples

    Sign in with email and password

    export default function EmailPasswordSignIn() {  const { loading, signIn } = useSignIn();  async function submit() {    if (loading) {      return;    }    await signIn.createStrategy('email')({      email: 'jane@example.com',      password: 'correct horse battery staple',    });  }  return <button onClick={submit}>Sign in with password</button>;}

    Resolve an identifier-first branch

    export default function IdentifierLookup() {  const { loading, signIn } = useSignIn();  async function checkIdentifier() {    if (loading) {      return;    }    const result = await signIn.identify('jane@example.com');    if (result.strategy === 'sso' && result.connection_id) {      await signIn.initEnterpriseSso(result.connection_id, window.location.href);    }  }  return <button onClick={checkIdentifier}>Continue</button>;}

    Prepare and complete email OTP verification

    export default function EmailOtpVerification() {  const { loading, signIn, signinAttempt } = useSignIn();  async function sendOtp() {    if (loading) {      return;    }    await signIn.createStrategy('email_otp')({      email: 'jane@example.com',    });    await signIn.prepareVerification({      strategy: 'email_otp',      redirectUri: window.location.href,    });  }  async function verifyOtp() {    await signIn.completeVerification('123456');  }  return (    <div>      <button onClick={sendOtp}>Send OTP</button>      <button onClick={verifyOtp} disabled={!signinAttempt}>        Verify OTP      </button>    </div>  );}

    Finish required profile fields

    export default function CompleteProfile() {  const { loading, signIn, signinAttempt } = useSignIn();  async function submitProfile() {    if (loading || !signinAttempt?.requires_completion) {      return;    }    await signIn.completeProfile({      first_name: 'Jane',      last_name: 'Doe',      username: 'janedoe',    });  }  return <button onClick={submitProfile}>Finish sign in</button>;}

    Start a passkey sign-in

    export default function PasskeySignIn() {  const { loading, signIn } = useSignIn();  async function signInWithPasskey() {    if (loading) {      return;    }    await signIn.createStrategy('passkey')();  }  return <button onClick={signInWithPasskey}>Use passkey</button>;}

    On this page