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

# Two-factor authentication

> TOTP enrollment, the two-step login flow, and recovery: RFC 6238 codes with single-use replay protection.

AroPay supports authenticator-app 2FA using **TOTP** (RFC 6238: SHA-1, 30-second
window, 6 digits), compatible with 1Password, Google Authenticator, Authy,
and friends. Codes are **single-use**: a code that just succeeded is rejected
if replayed within its window.

<Note>
  [Passkeys](/security/passkeys) count as strong MFA on their own; a passkey
  login never prompts for a TOTP code.
</Note>

## Enable 2FA

Enrollment is **session-only** (the dashboard flow lives in **Settings →
Security**) and takes two calls:

<Steps>
  <Step title="Set up: get the secret">
    ```bash theme={null}
    curl -s -X POST "$BASE/me/totp/setup" -b cookies.txt
    ```

    Returns the base32 `secret`, an `otpauthUri`, and a `qrDataUrl`; render
    the QR for scanning, or offer the secret for manual entry. Calling setup
    when 2FA is already enabled returns `400 totp_already_enabled`.
  </Step>

  <Step title="Enable: confirm with a live code">
    ```bash theme={null}
    curl -s -X POST "$BASE/me/totp/enable" -b cookies.txt \
      -H "content-type: application/json" \
      -d '{"code":"123456"}'
    ```

    Enforcement starts **only after** this succeeds (`totpEnabled: true` on
    your user object); an interrupted setup never locks anyone out.
  </Step>
</Steps>

## Logging in with 2FA

Password login becomes two steps:

```bash theme={null}
# Step 1: password (returns a pending token instead of a session)
curl -s "$BASE/auth/login" \
  -H "content-type: application/json" \
  -d '{"email":"you@company.com","password":"…"}'
# → { "ok": true, "data": { "requiresTotp": true, "pendingToken": "…" } }

# Step 2: exchange pending token + code for a session
curl -s "$BASE/auth/login/totp" -c cookies.txt \
  -H "content-type: application/json" \
  -d '{"pendingToken":"…","code":"123456"}'
```

The pending token is short-lived and single-purpose; no session exists until
step 2 succeeds. An expired token or wrong code returns `401`; just restart
from step 1.

## Disable 2FA

Disabling requires **both** your password and a live code; possession of an
unlocked session alone isn't enough:

```bash theme={null}
curl -s -X POST "$BASE/me/totp/disable" -b cookies.txt \
  -H "content-type: application/json" \
  -d '{"password":"…","code":"123456"}'
```

## Recovery

Lost the authenticator? Your Aro Media admin can clear 2FA on the account
(a `reset_2fa` action in the admin portal). You'll sign in with your password
alone and can re-enroll a new device.

## How secrets are protected

* TOTP secrets are **AES-256-GCM encrypted at rest**; they exist in plaintext
  only transiently during verification.
* Accepted codes are replay-guarded per time step.
* Login attempts remain subject to the global
  [rate limits and lockout](/security/rate-limits).
