> ## Documentation Index
> Fetch the complete documentation index at: https://thenile.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> Integrate Nile Auth with React applications

## Installation

`@niledatabase/react` contains components and hooks for using the API. It handles sessions, cookies, fetching data, and comes with built-in components to be used as either templates or directly in your application to handle common user tasks. It is designed to be used with `@niledatabase/server`, which handles API calls itself or forwards them to the regional API for your database.

```bash theme={null}
npm install @niledatabase/react @niledatabase/client
```

## Using the Auth Provider

`@niledatabase/react` comes with two providers, `<SignedIn />` and `<SignedOut />` which wrap a central `<SessionProvider />`. By default, they will fetch a session.

`<SignedIn />` will only render children if the user is logged in. Conversely, `<SignedOut />` will always render its children unless signed in.

```jsx theme={null}
<SignedIn>Jerry, hello!</SignedIn>
```

```jsx theme={null}
<SignedOut>No soup for you!</SignedOut>
```

It is also possible to be explicit and obtain the session server side. To do that, you would use the following:

```jsx theme={null}
import { SignedIn } from '@niledatabase/react';
import { nile } from '@/lib/nile';

export default async function SignUpPage() {
  const nileCtx = nile.withContext({ headers }); // headers from request
  const session = await nileCtx.auth.getSession();

  if (session.status === 'unauthenticated') {
    return <div>No soup for you!</div>;
  }

  return <SignedIn session={session}>Jerry, hello!</SignedIn>;
}
```

## Functions

### signIn

makes a `POST` request to the sign in endpoint. Expects a provider, and optional params for `callbackUrl`. For the cases of `credentials` (email + password) and `email`, you can opt out of redirection by passing `redirect: false` in the options

```jsx theme={null}
signIn('google', { callbackUrl: '/dashboard' });
signIn('credentials', { callbackUrl: '/dashboard' });
```

### signOut

makes a `POST` request to the sign out endpoint, with an optional params for `callbackUrl` to redirect the user, and `redirect` to leave the user on the page, but delete the session and notify the session providers the user has been logged out.

```jsx theme={null}
signOut({ callbackUrl: '/sign-in' }); // go back to some sign in page
signOut({ redirect: false }); // Log out, leave the user on the page they're on
```

## Hooks

### useSession

You can obtain the current session via `useSession()`. This must be called within a `<SignedIn />` or `<SignedOut />` provider.

```jsx theme={null}
import { useSession, UserInfo } from '@niledatabase/react';

export default function SignUpPage() {
  const session = useSession();

  if (session.status !== 'authenticated') {
    return <div>Loading...</div>;
  }

  return <UserInfo />;
}
```

### useTenantId

The `useTenantId` hook manages the current tenant ID, persisting it in cookies and refetching tenant data when necessary. A tenant id is accessible via `document.cookie` with the name `nile.tenant_id`. This cookie is used by the server side SDK to make requests to the auth service.

```tsx theme={null}
import { useTenantId } from '@niledatabase/react';

export default function TenantSelector() {
  const [tenantId, setTenantId] = useTenantId();

  return (
    <div>
      <p>Current Tenant: {tenantId ?? 'None'}</p>
      <button onClick={() => setTenantId('new-tenant-id')}>
        Change Tenant
      </button>
    </div>
  );
}
```

| Value         | Type                       | Description                       |
| ------------- | -------------------------- | --------------------------------- |
| `tenantId`    | `string \| undefined`      | The current tenant ID.            |
| `setTenantId` | `(tenant: string) => void` | Function to update the tenant ID. |

| Name     | Type                             | Default     | Description                  |
| -------- | -------------------------------- | ----------- | ---------------------------- |
| `params` | `HookProps & { tenant: Tenant }` | `undefined` | Initial tenant data.         |
| `client` | `QueryClient`                    | `undefined` | React Query client instance. |

```ts theme={null}
export type HookProps = {
  tenants?: Tenant[]; // a list of tenants
  onError?: (e: Error) => void; // failure callback
  baseUrl?: string; // fetch origin
};
```

* Initializes the tenant ID from `params.tenant.id`, if provided.
* If no tenant is found, it attempts to read from a cookie (`nile.tenant_id`).
* If no cookie exists, it triggers a refetch of tenants.
* Calling `setTenantId(tenantId)` updates both state and cookie.

### useTenants

The `useTenants` hook fetches a list of tenants from an API endpoint using React Query. It supports optional preloaded data and can be disabled to prevent automatic queries.

```tsx theme={null}
import { useTenants } from '@niledatabase/react';

export default function TenantList() {
  const { data: tenants, isLoading, error } = useTenants();

  if (isLoading) return <p>Loading tenants...</p>;
  if (error) return <p>Error loading tenants</p>;

  return (
    <ul>
      {tenants?.map((tenant) => (
        <li key={tenant.id}>{tenant.name}</li>
      ))}
    </ul>
  );
}
```

This hook returns the result of `useQuery`, which includes:

| Property    | Type                    | Description                           |
| ----------- | ----------------------- | ------------------------------------- |
| `data`      | `Tenant[] \| undefined` | List of tenants.                      |
| `isLoading` | `boolean`               | `true` while fetching data.           |
| `error`     | `Error \| null`         | Fetch error, if any.                  |
| `refetch`   | `() => void`            | Function to manually refetch tenants. |

**Parameters**

| Name     | Type                                     | Default     | Description                           |
| -------- | ---------------------------------------- | ----------- | ------------------------------------- |
| `params` | `HookProps & { disableQuery?: boolean }` | `undefined` | Hook configuration options.           |
| `client` | `QueryClient`                            | `undefined` | Optional React Query client instance. |

```ts theme={null}
export type HookProps = {
  tenants?: Tenant[];
  onError?: (e: Error) => void;
  baseUrl?: string;
};
```

* If `disableQuery` is `true`, the query is disabled.
* If `tenants` is provided and not empty (in the event of hydration), the query is also disabled.
* Otherwise, it fetches tenants from `${baseUrl}/api/tenants` using `fetch`.
* The request runs **only once** unless manually refetched.

### useEmailSignIn

The `useEmailSignIn` hook provides a mutation for signing in a user using nile auth. It allows customizing the request with callbacks and options for redirection.

```tsx theme={null}
import { useSignIn } from '@your-library/react';

export default function Login() {
  const signIn = useEmailSignIn({
    onSuccess: () => console.log('Login successful'),
    onError: (error) => console.error('Login failed', error),
    redirect: true,
  });

  return (
    <button onClick={() => signIn({ email: 'user@example.com' })}>
      Sign In
    </button>
  );
}
```

| Name           | Type                       | Default     | Description                              |
| -------------- | -------------------------- | ----------- | ---------------------------------------- |
| `onSuccess`    | `(data: Response) => void` | `undefined` | Callback after a successful sign-in.     |
| `onError`      | `(error: Error) => void`   | `undefined` | Callback if sign-in fails.               |
| `beforeMutate` | `(data: any) => any`       | `undefined` | Function to modify data before mutation. |
| `callbackUrl`  | `string`                   | `undefined` | URL to redirect after login.             |
| `redirect`     | `boolean`                  | `false`     | Whether to redirect after login.         |

* Calls `signIn('email', data)` with optional modifications via `beforeMutate`.
* Throws an error if authentication fails.
* Redirects if `redirect` is `true`.

### useMe

`useMe` is a React hook that fetches and returns the current authenticated user. It allows preloading a user via props or fetching from an API endpoint if no user is provided.

```tsx theme={null}
'use client';
import { useMe } from '@niledatabase/react';

export default function Profile() {
  const user = useMe();

  if (!user) return <p>Loading...</p>;

  return <div>Welcome, {user.name}!</div>;
}
```

| Name       | Type                        | Default     | Description                                     |
| ---------- | --------------------------- | ----------- | ----------------------------------------------- |
| `fetchUrl` | `string`                    | `/api/me`   | API endpoint to fetch the user data.            |
| `user`     | `User \| undefined \| null` | `undefined` | Initial user data, avoids fetching if provided. |

* If a `user` is passed in props, it is set immediately.
* If `user` is not provided, the hook fetches from `fetchUrl` and updates the state.
* The request runs **only once** when the component mounts.

### useResetPassword

The `useResetPassword` hook provides a way to handle password reset functionality. It sends reset requests to an authentication API and supports optional callbacks and preprocessing of request data.

```tsx theme={null}
import { useResetPassword } from '@niledatabase/react';

export default function ResetPasswordForm() {
  const resetPassword = useResetPassword({
    onSuccess: () => alert('Password reset successful'),
    onError: (err) => console.error('Error resetting password:', err),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    resetPassword({
      email: formData.get('email') as string,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        name="email"
        required
        placeholder="Enter your email"
      />
      <button type="submit">Reset Password</button>
    </form>
  );
}
```

| Name     | Type     | Default     | Description                 |
| -------- | -------- | ----------- | --------------------------- |
| `params` | `Params` | `undefined` | Hook configuration options. |

```ts theme={null}
export type Params = {
  onSuccess?: (data: Response) => void;
  onError?: (error: Error) => void;
  beforeMutate?: (data: MutateFnParams) => MutateFnParams;
  callbackUrl?: string;
  baseUrl?: string;
  fetchUrl?: string;
};
```

* Calls the API at `${baseUrl}/api/auth/reset-password` (or a custom `fetchUrl`).
* Uses **PUT** for password updates and **POST** for reset requests.
* Calls `onSuccess` or `onError` based on the request outcome.
* Runs a CSRF request when the hook is initialized.
* Allows modifying data before sending using `beforeMutate`.

### useSignUp

The `useSignUp` hook provides a way to handle user sign-up requests. It supports tenant creation, API customization, and session updates after successful registration.

```tsx theme={null}
import { useSignUp } from '@niledatabase/react';

export default function SignUpForm() {
  const signUp = useSignUp({
    onSuccess: () => alert('Sign-up successful!'),
    onError: (err) => console.error('Sign-up failed:', err),
    createTenant: true, // Optionally create a tenant with the email of the user
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    signUp({
      email: formData.get('email') as string,
      password: formData.get('password') as string,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required placeholder="Email" />
      <input type="password" name="password" required placeholder="Password" />
      <button type="submit">Sign Up</button>
    </form>
  );
}
```

| Name     | Type                     | Default     | Description                  |
| -------- | ------------------------ | ----------- | ---------------------------- |
| `params` | `Props`                  | `undefined` | Hook configuration options.  |
| `client` | `QueryClient` (optional) | `undefined` | React Query client instance. |

```ts theme={null}
export type Props = {
  onSuccess?: (data: Response, variables: unknown) => void; // success callback
  onError?: (error: Error) => void; // error callback
  beforeMutate?: (data: SignUpInfo) => SignUpInfo; // optional modifications to the data prior to the API call
  callbackUrl?: string; // where the server should redirect users upon successful login
  baseUrl?: string; // configure fetch origin
  createTenant?: boolean | string; // if boolean, will create a tenant named with the user's email, else will be whatever name is provided (maps to /api/tenants?newTenantName=<name>)
};
```

```ts theme={null}
export type SignUpInfo = {
  email: string;
  password: string;
  tenantId?: string;
  newTenantName?: string;
  fetchUrl?: string;
};
```

* Sends a `POST` request to `/api/signup` (or a custom `fetchUrl`).
* If `createTenant` is `true`, assigns `newTenantName` as the user’s email.
* If `createTenant` is a string, it is used as the tenant name.
* After a successful sign-up:
  * Updates the session.
  * Redirects to `callbackUrl` if provided, otherwise reloads the page.
* Prefetches authentication providers and CSRF tokens on mount.

### useSignIn

The `useSignIn` hook provides a simple way to authenticate users. It supports pre-processing login data before submission and handles authentication via credentials.

```tsx theme={null}
import { useSignIn } from '@niledatabase/react';

export default function SignInForm() {
  const signIn = useSignIn({
    onSuccess: () => alert('Login successful!'),
    onError: (err) => console.error('Login failed:', err),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    signIn({
      email: formData.get('email') as string,
      password: formData.get('password') as string,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required placeholder="Email" />
      <input type="password" name="password" required placeholder="Password" />
      <button type="submit">Sign In</button>
    </form>
  );
}
```

| Name     | Type    | Default     | Description                 |
| -------- | ------- | ----------- | --------------------------- |
| `params` | `Props` | `undefined` | Hook configuration options. |

```ts theme={null}
export type Props = {
  onSuccess?: () => void;
  onError?: (error: Error) => void;
  beforeMutate?: (data: LoginInfo) => LoginInfo;
  callbackUrl?: string;
};
```

```ts theme={null}
export type LoginInfo = {
  email: string;
  password: string;
};
```

***

* Sends a sign-in request using NextAuth's `signIn('credentials', data)`.
* If `beforeMutate` is provided, it modifies the login data before the request.
* Calls `onSuccess` if login succeeds.
* Calls `onError` if login fails.

## Multi-factor (Client)

Client-side MFA enrollment, challenge, and removal flows powered by the React SDK. The `User` object (obtained via `useSession`) will include a `multiFactor` property when MFA is enabled for the user.

### Features

* Authenticator app or email one-time-code enrollment against `/auth/mfa`
* Recovery codes for authenticator challenges with remaining-count feedback
* Redirect-aware helpers that work with custom routers or default navigation (`ChallengeRedirect`)
* UI building blocks (`MultiFactor*` components) plus a lightweight hook
* Optional low-level `mfa` helper for bespoke prompts or headless flows

### Overview

Nile Auth exposes a single `/auth/mfa` endpoint for starting MFA setup, completing challenges, and removing an enrolled method. The React SDK wraps the endpoint in `useMultiFactor` and UI components that parse server responses into ready-to-render experiences.

Tokens returned from setup or sign-in responses must be echoed back on subsequent calls (challenge verification or removal). When the backend needs the browser to redirect, the helper returns `{ url: string }` (a `ChallengeRedirect`) so you can route accordingly.

### Installation

```sh theme={null}
npm install @niledatabase/react @niledatabase/client
```

### Quick start

#### Enroll with an authenticator app

```tsx theme={null}
import { useMultiFactor, MultiFactorAuthenticator } from '@niledatabase/react';

export function MfaEnrollment() {
  const { setup, loading, startSetup } = useMultiFactor({
    method: 'authenticator',
    currentMethod: null,
    onRedirect: (url) => window.location.assign(url), // optional
  });

  return (
    <>
      <button onClick={startSetup} disabled={loading}>
        {loading ? 'Starting...' : 'Enable authenticator MFA'}
      </button>

      {setup?.scope === 'setup' && setup.method === 'authenticator' ? (
        <MultiFactorAuthenticator
          setup={setup}
          onError={console.error}
          onSuccess={(scope) => scope === 'setup' && window.location.reload()}
        />
      ) : null}
    </>
  );
}
```

<iframe src="https://storybook.thenile.dev/iframe.html?globals=&args=&id=multifactor-setupauthenticator--default" width="100%" height="520px" className="rounded-xl" />

#### Handle a challenge prompt (sign-in or removal)

```tsx theme={null}
import { useMultiFactor, MultiFactorChallenge } from '@niledatabase/react';

export function ChallengePrompt({ existingToken }: { existingToken: string }) {
  const { setup, startDisable } = useMultiFactor({
    method: 'authenticator',
    currentMethod: 'authenticator',
  });

  return (
    <>
      {/* Example: Disabling MFA requires a challenge verification first */}
      <button onClick={startDisable}>Disable MFA</button>

      {setup?.scope === 'challenge' ? (
        <MultiFactorChallenge
          payload={setup}
          isEnrolled
          message="Enter code from your authenticator app"
          onSuccess={() => window.location.replace('/app')}
        />
      ) : null}
    </>
  );
}
```

<iframe src="https://storybook.thenile.dev/iframe.html?globals=&args=&id=multifactor-challengecontent--authenticator-challenge" width="100%" height="420px" className="rounded-xl" />

### API

#### `useMultiFactor(options)`

| Name                  | Type                                                       | Default                           | Description                                                                             |
| --------------------- | ---------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------- |
| `method`              | `'authenticator' \| 'email'`                               | *(required)*                      | MFA mechanism to enable or disable.                                                     |
| `currentMethod`       | `'authenticator' \| 'email' \| null`                       | `null`                            | Currently enrolled method; blocks switching until disabled.                             |
| `onRedirect`          | `(url: string) => void`                                    | `window.location.assign`          | Optional handler when the backend responds with a `url` to visit (`ChallengeRedirect`). |
| `onChallengeRedirect` | `(params: { token; method; scope; destination? }) => void` | Internal `/mfa/prompt` navigation | Override the default challenge redirect builder.                                        |

Returns `{ setup, loading, errorType, startSetup, startDisable }`.

* `setup`: `null` or an MFA payload. For authenticator: `{ method: 'authenticator'; token; scope; otpauthUrl?; secret?; recoveryKeys? }`. For email: `{ method: 'email'; token; scope; maskedEmail? }`.
* `startSetup()`: begins enrollment (POST `/auth/mfa`); `setup.scope` will be `"setup"` or `"challenge"` if a verification step is required.
* `startDisable()`: starts removal for the given method. If verification is required, `setup.scope` will be `"challenge"`.
* `errorType`: one of `setup`, `disable`, `parseSetup`, `parseDisable`, or `null` for success.

### Components

* `MultiFactorAuthenticator` — renders QR code, recovery keys, and a verification form.\
  Props: `setup: AuthenticatorSetup`, `onError(message: string | null)`, `onSuccess(scope: 'setup' | 'challenge')`.
* `MultiFactorEmail` — shows masked email messaging and a verification form.\
  Props: `setup: EmailSetup`, `onSuccess(scope: 'setup' | 'challenge')`.
* `MultiFactorChallenge` — shared challenge UI for either method (used for disable flows or sign-in prompts).\
  Props: `payload: { token: string; scope: ChallengeScope; method: MfaMethod }`, `message: string`, `isEnrolled: boolean`, `onSuccess(scope)`.

### Error handling

* Non-200 responses are coerced into `{ url: string }` (`ChallengeRedirect`) with an `error` search param. `MfaVerifyForm` and the examples above parse this for user-friendly messaging.
* If `code` is missing or shorter than 6 digits, the React forms set a validation error before calling the API.

### Additional notes

* Codes are expected to be 6 digits for authenticator/email verification; recovery codes are string tokens issued during setup.
* Challenge tokens expire; expect 410 responses for stale tokens and 404 for unknown challenges.
* Email MFA may return a `maskedEmail` and require the same token on verification and disable flows.

## Related Topics

* [MFA Concepts](/auth/concepts/multifactor)
* [Next.js Integration](/auth/frameworks/nextjs)
* [Components](/auth/components/signin)
