# Sign In: the Second Step

> Coming soon, upon request — this feature is turned on for your tenant by the Aurea team. [Contact us](https://www.aureahub.com/contact)

After the password, the code: the answer is the login's, with the tokens.

## Overview

- When [Login](https://docs.aureahub.com/docs/auth-login.md) answers `401` with `details.code` `MFA_REQUIRED` — or a password reset answers it in its `200` ([Forgot Password](https://docs.aureahub.com/docs/auth-forgot-password.md) › Complete the reset) —, send its `details.mfaToken` here within 5 minutes, with `code` (six digits of the authenticator app) **or** `recoveryCode` (one of the recovery codes) — not both: `422`.
- **Signed as the login is**: the tenant's HMAC headers, or a server token in `Authorization` ([Authentication](https://docs.aureahub.com/docs/authentication.md)). No bearer token.
- `200`: `accessToken`, `refreshToken`, `expiresIn` and `user`, as the login answers them.
- **An `mfaToken` signs in once.** Sent again after it gave the tokens, it answers `401` `MFA_CHALLENGE_INVALID`, as an expired one does: sign in again from the password. Of two answers sent at the same time, one signs in and the other gets that `401`.
- `401` `MFA_CODE_INVALID` for a wrong or already used code — ask again with the same `mfaToken` while it is valid: a refused code does not use it up. It counts as a failed sign-in (`429` with `Retry-After` where Aurea throttles them). `401` `MFA_CHALLENGE_INVALID` once the `mfaToken` has expired or has signed in: sign in again. A suspended account answers `401` `Account is not active`; a frozen one `423`.
- 20 requests per minute per client IP. [Two-Factor Sign-In](https://docs.aureahub.com/docs/guide-two-factor.md) walks through the flow.

## Endpoint

### `POST /v1/auth/mfa/verify`

Authentication: signed by the tenant's server — headers `x-tenant-api-key`, `x-timestamp`, `x-signature` (see Authentication → Tenant signature). No user token.

Completes a sign-in with the second factor. Requires tenant HMAC signing headers.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `mfaToken` | string | yes | `details.mfaToken` of the login's `401`, at most 2048 characters |
| `code` | string | no | Six digits from the authenticator app |
| `recoveryCode` | string | no | One of the recovery codes, 10–16 characters |

**Responses**

`200` Signed in

```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 900,
  "user": {
    "id": "6a1c9e3f-2b7d-4f50-8e4a-0c5d3b9f7e12",
    "username": "marco.cfo",
    "email": "marco@acme.example",
    "qrCode": "<qr-code>",
    "role": "tenantadmin",
    "createdAt": "2026-09-01T10:00:00.000Z"
  }
}
```

`401` Wrong code

```json
{
  "statusCode": 401,
  "error": "UnauthorizedError",
  "message": "The code is not correct",
  "details": { "code": "MFA_CODE_INVALID" }
}
```

`401` Expired or used

```json
{
  "statusCode": 401,
  "error": "UnauthorizedError",
  "message": "The sign-in has expired: sign in again",
  "details": { "code": "MFA_CHALLENGE_INVALID" }
}
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/mfa/verify \
  -H "Content-Type: application/json" \
  -H "x-tenant-api-key: <your-tenant-api-key>" \
  -H "x-timestamp: <unix-seconds>" \
  -H "x-signature: <hex-hmac-sha256>" \
  -d '{"mfaToken":"<details.mfaToken>","code":"492039"}'
```

---

Web version: https://docs.aureahub.com/#mfa-verify
