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

# Two-factor auth

> Add TOTP second-factor authentication via the `twoFactor` plugin. QR code enrollment, 6-digit verification, and backup codes for any RFC 6238 authenticator.

The `twoFactor()` plugin adds TOTP (time-based one-time password) support compatible with Google Authenticator, Authy, 1Password, and any RFC 6238 app. Users enroll once by scanning a QR code, then provide a 6-digit code on every sign-in.

## Setup

<Steps>
  <Step>
    ### Install

    ```bash theme={"system"}
    pnpm add kavachos
    ```
  </Step>

  <Step>
    ### Add the plugin

    ```typescript title="lib/kavach.ts" theme={"system"}
    import { createKavach } from 'kavachos';
    import { emailPassword } from 'kavachos/auth';
    import { twoFactor } from 'kavachos/auth'; // [!code highlight]

    const kavach = await createKavach({
      database: { provider: 'postgres', url: process.env.DATABASE_URL! },
      secret: process.env.KAVACH_SECRET!,
      baseUrl: 'https://auth.example.com',
      plugins: [
        emailPassword(),
        twoFactor({ // [!code highlight]
          issuer: 'My App',  // Shown in the authenticator app // [!code highlight]
        }), // [!code highlight]
      ],
    });
    ```

    <Info>
      `twoFactor()` works with any primary auth method. Pair it with OAuth providers or magic link as well as email/password.
    </Info>
  </Step>
</Steps>

## Enrollment flow

All enrollment endpoints require an active session.

<Steps>
  <Step>
    ### Generate a TOTP secret

    `POST /auth/2fa/enroll`

    ```typescript theme={"system"}
    const res = await fetch('/auth/2fa/enroll', {
      method: 'POST',
      credentials: 'include',
    });

    const { secret, qrCodeUrl, backupCodes } = await res.json(); // [!code highlight]
    ```

    Render `qrCodeUrl` as a QR code image in your UI. The user scans it with their authenticator app. Show `backupCodes` once at this point, they cannot be retrieved again.
  </Step>

  <Step>
    ### Confirm with the first code

    Ask the user to enter the code their authenticator app shows to confirm enrollment:

    `POST /auth/2fa/verify`

    ```typescript theme={"system"}
    await fetch('/auth/2fa/verify', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code: '482910' }),
    });
    ```

    A successful response activates 2FA on the account.
  </Step>
</Steps>

## Login verification

When a user with 2FA enabled signs in, the primary auth endpoint returns a partial response instead of a session:

```json theme={"system"}
{ "status": "two_factor_required", "challengeToken": "..." }
```

Submit the TOTP code along with the challenge token to complete sign-in:

`POST /auth/2fa/verify`

```typescript theme={"system"}
const res = await fetch('/auth/2fa/verify', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    challengeToken,   // From the primary auth response // [!code highlight]
    code: '482910',   // From the user's authenticator app // [!code highlight]
  }),
});
```

On success, a session cookie is set.

## Backup codes

Ten single-use backup codes are generated at enrollment. Each is a 10-character alphanumeric string. A backup code can be submitted in place of a TOTP code at the verify endpoint, useful when the user has lost access to their authenticator app.

### Regenerate backup codes

`POST /auth/2fa/backup-codes`

This invalidates all existing backup codes and issues a new set. Requires an active session.

```typescript theme={"system"}
const res = await fetch('/auth/2fa/backup-codes', {
  method: 'POST',
  credentials: 'include',
});

const { backupCodes } = await res.json();
```

## Disabling 2FA

`POST /auth/2fa/disable`

Requires the user's current TOTP code as confirmation:

```typescript theme={"system"}
await fetch('/auth/2fa/disable', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code: '482910' }),
});
```

## Check enrollment status

`GET /auth/2fa/status`

```typescript theme={"system"}
const res = await fetch('/auth/2fa/status', { credentials: 'include' });
const { enabled, enrolledAt } = await res.json();
```

## Endpoints

All endpoints require an authenticated session unless noted.

| Endpoint                      | Description                             |
| ----------------------------- | --------------------------------------- |
| `POST /auth/2fa/enroll`       | Generate a TOTP secret and backup codes |
| `POST /auth/2fa/verify`       | Confirm enrollment or complete login    |
| `POST /auth/2fa/disable`      | Disable 2FA, requires current code      |
| `GET /auth/2fa/status`        | Check whether 2FA is enabled            |
| `POST /auth/2fa/backup-codes` | Regenerate backup codes                 |

## Options

| Option              | Type                               | Default  | Description                                         |
| ------------------- | ---------------------------------- | -------- | --------------------------------------------------- |
| `issuer`            | `string`                           | required | App name shown in the authenticator                 |
| `enforce`           | `boolean`                          | `false`  | Require 2FA for all users                           |
| `shouldEnforce`     | `(user: User) => Promise<boolean>` | ,        | Per-user enforcement logic                          |
| `backupCodeCount`   | `number`                           | `10`     | Number of backup codes generated at enrollment      |
| `challengeTokenTtl` | `number`                           | `300`    | Seconds to complete verification after primary auth |
