POST /v1/auth/mfa/enable
Confirm the user can produce valid TOTP codes for the secret minted by
/setup. On success, flips mfa_enabled = true and returns 10 fresh
recovery codes (shown exactly once).
::: tip Auth Required: cookie or Bearer. :::
Request
POST /v1/auth/mfa/enable
| Header | Required | Notes |
|---|---|---|
Cookie: identsphere_at=... OR Authorization: Bearer ... | yes | — |
Content-Type: application/json | yes | — |
Body
{ "code": "123456" }
| Field | Type | Required | Notes |
|---|---|---|---|
code | string | yes | 6–8 chars. The current TOTP code from the user's authenticator. |
Response
200 OK
{
"recovery_codes": [
"Yj7nKp2qA8",
"M4xRv9bC1z",
"Q3wPe5sL8h",
"T6yUu2iR4o",
"B7nMx5cV1z",
"L8kJh4gF2d",
"S3aQ5wE9rT",
"F2dG3hY4uI",
"Z1xC4vB7nM",
"P9oL5kJ8hG"
]
}
::: warning Show these exactly once The plaintext codes are returned here and NEVER again. Display them, prompt the user to save them somewhere safe (password manager, printed sheet), and discard from client state. :::
Error responses
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | Code is missing, too short, or too long. MFA setup has not been started (/setup wasn't called first). |
| 401 | authentication_required | No valid auth credential, OR the TOTP code doesn't match. The handler accepts ±1 30-second window of clock skew. |
| 404 | not_found | User no longer exists. |
| 409 | conflict | MFA is already enabled. |
| 500 | internal_error | TOTP build or DB failure. |
Example: curl
curl -X POST https://auth.example.com/v1/auth/mfa/enable \
-H 'Content-Type: application/json' \
-b cookies.txt \
-d '{"code":"123456"}'
Example: TypeScript (@identsphere/react)
import { useMfaEnable } from '@identsphere/react';
function MfaEnrollStep2() {
const enable = useMfaEnable();
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const code = new FormData(e.currentTarget).get('code') as string;
const { recovery_codes } = await enable.mutateAsync({ code });
// Show recovery_codes to the user, prompt them to save
}}
>
<input name="code" placeholder="123456" />
<button type="submit">Enable MFA</button>
</form>
);
}
Notes
- Recovery codes are stored in the database as SHA-256 hashes; the SDK never retains the plaintext after this response.
- Any prior recovery codes for the user are wiped before the new batch is inserted — there's exactly one valid batch at any time.
- An audit entry (
auth.mfa.enabled) is recorded asynchronously. - After enabling, future logins for this user will return
status: "mfa_required"and require completion ofPOST /v1/auth/mfa/challenge.