# Two-factor authentication

An authenticator app for the signed-in account: setting one up, the recovery codes, replacing or removing it, and the second-factor step of signing in. A subpath of its own, installed only where an app offers it. It exports SignIn and User as the auth namespaces with its parts added, so a screen writes SignIn.TwoFactorCode and User.TwoFactor.Root, importing both from here.

Import: @slices.sh/auth/headless/two-factor

Public parts: 38

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/auth@^0.1.10
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/auth.md

## Anatomy

### Composition

```text
User.Root
  User.TwoFactor.Root
    User.TwoFactor.Step name="off"
      User.TwoFactor.Disabled
        User.TwoFactor.Available
        User.TwoFactor.Status name="off"
        User.TwoFactor.Begin
    User.TwoFactor.Step name="confirm"
      User.TwoFactor.Dialog
        User.TwoFactor.Form
          User.TwoFactor.Account
          User.TwoFactor.Label
          User.TwoFactor.Password
          User.TwoFactor.Start
          User.TwoFactor.Confirm
          User.TwoFactor.Regenerate
          User.TwoFactor.Disable
          User.TwoFactor.Cancel
          User.TwoFactor.UseAnotherAccount
    User.TwoFactor.Step name="scan"
      User.TwoFactor.Setup
        User.TwoFactor.Qr
        User.TwoFactor.Secret
        User.TwoFactor.OpenInApp
        User.TwoFactor.Code
        User.TwoFactor.Enable
      User.TwoFactor.Error
    User.TwoFactor.Step name="verified"
      User.TwoFactor.ShowCodes
    User.TwoFactor.Step name="codes"
      User.TwoFactor.BackupCode
      User.TwoFactor.CopyCodes
      User.TwoFactor.DownloadCodes
      User.TwoFactor.PrintCodes
      User.TwoFactor.CodesStored
      User.TwoFactor.Finish
    User.TwoFactor.Step name="on"
      User.TwoFactor.Enabled
        User.TwoFactor.Replace
        User.TwoFactor.NewCodes
        User.TwoFactor.Remove
        User.TwoFactor.Status name="on"
    User.TwoFactor.Step name="unavailable"
      User.TwoFactor.Provider
      User.TwoFactor.NewPassword
      User.TwoFactor.AddPassword
      User.TwoFactor.Status name="unavailable"
```

## Set up an authenticator

```tsx
"use client"

import "@/services"
import { User } from "@slices.sh/auth/headless/two-factor"

export function Example() {
  return (
    <User.Root>
      <User.TwoFactor.Root>
        <User.TwoFactor.Step name="off">
          <User.TwoFactor.Begin>Enable two-factor</User.TwoFactor.Begin>
        </User.TwoFactor.Step>
        <User.TwoFactor.Step name="confirm">
          <User.TwoFactor.Form>
            <User.TwoFactor.Password aria-label="Current password" />
            <User.TwoFactor.Confirm>Continue</User.TwoFactor.Confirm>
          </User.TwoFactor.Form>
        </User.TwoFactor.Step>
        <User.TwoFactor.Step name="scan">
          <User.TwoFactor.Form>
            <User.TwoFactor.Qr />
            <User.TwoFactor.Secret />
            <User.TwoFactor.Code aria-label="Authenticator code" />
            <User.TwoFactor.Enable>Verify</User.TwoFactor.Enable>
          </User.TwoFactor.Form>
        </User.TwoFactor.Step>
        <User.TwoFactor.Step name="verified">
          <User.TwoFactor.ShowCodes>
            Save recovery codes
          </User.TwoFactor.ShowCodes>
        </User.TwoFactor.Step>
        <User.TwoFactor.Step name="codes">
          <User.TwoFactor.BackupCode />
          <User.TwoFactor.CopyCodes>Copy codes</User.TwoFactor.CopyCodes>
          <label>
            <User.TwoFactor.CodesStored />I saved these codes
          </label>
          <User.TwoFactor.Finish>Done</User.TwoFactor.Finish>
        </User.TwoFactor.Step>
        <User.TwoFactor.Step name="on">
          Your account is protected.
        </User.TwoFactor.Step>
        <User.TwoFactor.Error />
      </User.TwoFactor.Root>
    </User.Root>
  )
}

```

Enable password and two-factor. This wizard assumes the signed-in account already has a password.

## Security status

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { User } from "@slices.sh/auth/headless/two-factor"

export function Example() {
  return (
    <User.Root>
      <User.TwoFactor.Available>
        <User.TwoFactor.Enabled>Two-factor is on.</User.TwoFactor.Enabled>
        <User.TwoFactor.Disabled>
          Two-factor is off.
        </User.TwoFactor.Disabled>
      </User.TwoFactor.Available>
    </User.Root>
  )
}

```

Enable password and twoFactor in the auth provider. These gates read the signed-in user's status.

## Setup in progress

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { User } from "@slices.sh/auth/headless/two-factor"

export function Example() {
  return (
    <User.Root>
      <User.TwoFactor.Setup>
        Finish setting up your authenticator.
      </User.TwoFactor.Setup>
      <User.TwoFactor.Enabled>
        Your account is protected.
      </User.TwoFactor.Enabled>
    </User.Root>
  )
}

```

Setup and Enabled are mutually exclusive states of the signed-in account.

## Complete API reference

### User.TwoFactor.Dialog

content

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Dialog requires User.TwoFactor.Root.

```tsx
PartProps<null>
```

Source: @slices.sh/auth/src/headless/two-factor/dialog.tsx

### User.TwoFactor.Status

gate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps & { name: "off" | "on" | "unavailable" }
```

Source: @slices.sh/auth/src/headless/two-factor/status.tsx

### User.TwoFactor.Available

gate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps
```

Source: @slices.sh/auth/src/headless/two-factor/available.tsx

### User.TwoFactor.Root

scope

The wizard around the steps: the fields it collects, and how far it has got.

They live here rather than in a form because setting an authenticator up spans several
screens — confirm the password, scan the code, put the recovery codes away — and each one
is a gate that mounts and unmounts.

Import: @slices.sh/auth/headless/two-factor

```tsx
RootProps & AuthRootProps
```

Source: @slices.sh/auth/src/headless/two-factor/root.tsx

### User.TwoFactor.Step

gate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps & { name: TwoFactorStep | readonly TwoFactorStep[]; render?: ReactElement }
```

Source: @slices.sh/auth/src/headless/two-factor/step.tsx

### User.TwoFactor.Form

action · Element: form

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<"form", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/form.tsx

### User.TwoFactor.Disabled

gate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps
```

Source: @slices.sh/auth/src/headless/two-factor/disabled.tsx

### User.TwoFactor.Setup

gate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps
```

Source: @slices.sh/auth/src/headless/two-factor/setup.tsx

### User.TwoFactor.Enabled

gate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Import: @slices.sh/auth/headless/two-factor

```tsx
GateProps
```

Source: @slices.sh/auth/src/headless/two-factor/enabled.tsx

### User.TwoFactor.Label

content · Element: label

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Label field=

```tsx
PartProps<"label"> & { field: TwoFactorField }

export type TwoFactorField = keyof typeof FIELDS
```

Source: @slices.sh/auth/src/headless/two-factor/label.tsx

### User.TwoFactor.Password

input · Element: input

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Password must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/password.tsx

### User.TwoFactor.Start

action · Element: button

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Start must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/start.tsx

### User.TwoFactor.Qr

content · Element: svg

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<"svg">
```

Source: @slices.sh/auth/src/headless/two-factor/qr.tsx

### User.TwoFactor.Secret

content

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<null>
```

Source: @slices.sh/auth/src/headless/two-factor/secret.tsx

### User.TwoFactor.Code

input · Element: input

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Code must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/code.tsx

### User.TwoFactor.Enable

action · Element: button

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Enable must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/enable.tsx

### User.TwoFactor.BackupCode

content · Element: li

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

```tsx
BackupCodeProps

type BackupCodeProps = PartProps<"li", { code: string }>
```

Source: @slices.sh/auth/src/headless/two-factor/backup-code.tsx

### User.TwoFactor.Regenerate

action · Element: button

A new set of backup codes. Each old one stops working, which is the point: an account
that has spent its way through the set it was given at setup has nothing left to use.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

User.TwoFactor.Regenerate must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/regenerate.tsx

### User.TwoFactor.Disable

action · Element: button

From the on state. Replace, Remove and NewCodes each pass through confirm. Disable turns two-factor off from a password outside the wizard. Error is <div role="alert"> with { code, message }, the last failure of starting, enabling, disabling, new codes or adding a password.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Disable must be inside <User.TwoFactor.Form>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/disable.tsx

### User.TwoFactor.Error

content · Element: div

From the on state. Replace, Remove and NewCodes each pass through confirm. Disable turns two-factor off from a password outside the wizard. Error is <div role="alert"> with { code, message }, the last failure of starting, enabling, disabling, new codes or adding a password.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<"div", { code: AuthErrorCode; message: string }>
```

Source: @slices.sh/auth/src/headless/two-factor/error.tsx

### User.TwoFactor.Begin

action · Element: button

Starts setting an authenticator up, which asks for the password first.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Begin must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/begin.tsx

### User.TwoFactor.Account

input · Element: input

The address the authenticator will be set up for, read-only.

Shown because a password prompt with no account beside it is a question about which
account, and `autocomplete=username` is what lets a password manager offer the right
password for the field underneath.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Account must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"input">
```

Source: @slices.sh/auth/src/headless/two-factor/account.tsx

### User.TwoFactor.UseAnotherAccount

action · Element: button

Signs out from the confirm step. Somebody who does not know this account's password is
usually somebody looking at the wrong account, and the only way on from there is out.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/use-another-account.tsx

### User.TwoFactor.Confirm

action · Element: button

The password, for whatever the wizard was asked for. One part rather than four, because
from the person's side this is one act: proving the account is theirs before anything
about how they sign in changes.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Confirm must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/confirm.tsx

### User.TwoFactor.Cancel

action · Element: button

Leaves the wizard, forgetting the secret this browser was shown.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Cancel must be inside <User.TwoFactor.Root>.

User.TwoFactor.Cancel requires User.TwoFactor.Root.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/cancel.tsx

### User.TwoFactor.OpenInApp

content · Element: a

The `otpauth:` link the authenticator app registered itself for.

On a phone there is no second screen to point a camera at, so scanning is impossible and
the link is the only way in short of typing the secret.

Import: @slices.sh/auth/headless/two-factor

```tsx
PartProps<"a">
```

Source: @slices.sh/auth/src/headless/two-factor/open-in-app.tsx

### User.TwoFactor.ShowCodes

action · Element: button

Leaves the success step for the recovery codes, which are shown once.

Where a backend hands out none it is the way out of the wizard instead: the step after
this one would be empty, and there would be nothing to press on it.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

User.TwoFactor.ShowCodes must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/show-codes.tsx

### User.TwoFactor.CopyCodes

action · Element: button

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

```tsx
PartProps<"button", { copied: boolean; failed: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/copy-codes.tsx

### User.TwoFactor.DownloadCodes

content · Element: a

The codes as a file. A `data:` URL rather than a blob one, because a blob URL has to be
revoked and an anchor nobody clicks would leak it for the life of the page.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

```tsx
PartProps<"a">
```

Source: @slices.sh/auth/src/headless/two-factor/download-codes.tsx

### User.TwoFactor.PrintCodes

action · Element: button

Paper, which is the one place a recovery code survives losing the phone and the laptop.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/print-codes.tsx

### User.TwoFactor.CodesStored

input · Element: input

Says the codes are somewhere safe, which is what finishes setup.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

User.TwoFactor.CodesStored must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"input", { checked: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/codes-stored.tsx

### User.TwoFactor.Finish

action · Element: button

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

User.TwoFactor.Finish must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/finish.tsx

### User.TwoFactor.Replace

action · Element: button

Swaps the authenticator for another one, by turning this one off and setting one up
again: Better Auth keeps one TOTP per account and refuses a second with
TOTP_ALREADY_ENABLED, so there is no one-step replacement to offer.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Replace must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/replace.tsx

### User.TwoFactor.Remove

action · Element: button

Asks to turn the authenticator off, which is confirmed with the password.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.Remove must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/remove.tsx

### User.TwoFactor.NewCodes

action · Element: button

Asks for a new set of recovery codes. Each code is spent when it is used, so an account
that has worked its way through the set it was given has no way back in left.

Import: @slices.sh/auth/headless/two-factor

User.TwoFactor.NewCodes must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button">
```

Source: @slices.sh/auth/src/headless/two-factor/new-codes.tsx

### User.TwoFactor.Provider

content

Who this account signs in through, so the explanation can name them rather than say
"a provider". The ids come out as the backend spells them, `google` and `github`, and
whoever writes the sentence gives them their capital letters.

Import: @slices.sh/auth/headless/two-factor

Capability: methods: password

```tsx
PartProps<null, { provider: string | null; providers: readonly string[] }>
```

Source: @slices.sh/auth/src/headless/two-factor/provider.tsx

### User.TwoFactor.NewPassword

input · Element: input

The password an account that arrived through a provider is being given.

Import: @slices.sh/auth/headless/two-factor

Capability: methods: password

User.TwoFactor.NewPassword must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/new-password.tsx

### User.TwoFactor.AddPassword

action · Element: button

Gives the account a password, which is what makes a second factor possible for it: the
code is asked for on a password sign-in, and there was no password sign-in before this.

Import: @slices.sh/auth/headless/two-factor

Capability: methods: password

User.TwoFactor.AddPassword must be inside <User.TwoFactor.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/add-password.tsx

### User.Root

scope

Says which auth the User parts below read, and whether they may do without it. The parts need
no root: left out, they read the nearest auth root's config, or "default", and render nothing
without auth. `<User.Root enabled="optional">` is for the one who wants that said in the code.

Import: @slices.sh/auth/headless

```tsx
RootProps & AuthRootProps
```

Alternate import: User.Root from @slices.sh/auth/headless/user

Alternate import: User.Root from @slices.sh/auth/headless/passkeys

Alternate import: User.Root from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/root.tsx
