Multi-factor authentication
Purpose: a second factor for your users' accounts, run entirely on the hosted pages: enrollment, the sign-in challenge, and recovery. You choose the policy per tenant; there is nothing to build.
The factor is TOTP — any standard authenticator app, enrolled by QR code or by typing the secret. Enrollment issues 10 single-use recovery codes, shown once at that moment and stored only as hashes, so a user who loses the authenticator can still get in. Enrolled secrets are sealed with authenticated encryption at rest, and challenge attempts are rate-limited.
Where your users meet it
Enrollment and management live on the hosted
/securitypage: set up two-factor, see its status, and turn it off — when your policy allows either (see below).The challenge happens at sign-in, before any session exists. Password, social, and passwordless sign-in all pass through the same gate, so no method skips the second factor.
Recovery codes are accepted at the challenge in place of a TOTP code; each works once.
The policy
One tenant-level knob, mfaPolicy, with three values:
| Value | Meaning |
|---|---|
off | No one can newly enroll. A factor that is already enrolled is still challenged — see below. |
optional | Users may enroll themselves; enrolled users are challenged at sign-in. The default. |
required | Everyone is challenged: an unenrolled user is taken through enrollment at their next sign-in, and users cannot turn their own factor off. |
Switching to off does not bypass enrolled factors. A user who set up
two-factor is still challenged at sign-in until their factor is removed — by
them on /security, or by you (below). A policy flip silently disabling a
protection a user chose for themselves is the wrong failure mode, so the policy
governs enrollment, not whether an existing factor counts.
Resetting a user's MFA
When someone loses both the authenticator and their recovery codes, an
administrator resets them from the user's page in the dashboard (Users → the
person → Danger zone) or through the management API. The reset removes the
user's factors and revokes their sessions, so the next sign-in starts clean
— and re-enrolls, if your policy is required.
| Method | Path | Scope |
|---|---|---|
POST | /api/identity/{tenantId}/users/{id}/reset-mfa | identity:users:write |
const tenantId = Deno.env.get("UDIBO_TENANT_ID");
const userId = Deno.env.get("USER_ID");
const response = await fetch(
`https://www.udibo.com/api/identity/${tenantId}/users/${userId}/reset-mfa`,
{
method: "POST",
headers: { authorization: `Bearer ${Deno.env.get("UDIBO_API_TOKEN")}` },
},
);
if (!response.ok) throw new Error(`reset failed: ${response.status}`);
const result: { success: true; sessionsRevoked: number } = await response
.json();
console.log(`sessions revoked: ${result.sessionsRevoked}`);Setting the policy
In the dashboard the policy lives under Settings; programmatically it is one field on the tenant:
| Method | Path | Scope |
|---|---|---|
PATCH | /api/identity/tenants/{id} | identity:tenants:write |
const tenantId = Deno.env.get("UDIBO_TENANT_ID");
const response = await fetch(
`https://www.udibo.com/api/identity/tenants/${tenantId}`,
{
method: "PATCH",
headers: {
authorization: `Bearer ${Deno.env.get("UDIBO_API_TOKEN")}`,
"content-type": "application/json",
},
body: JSON.stringify({ mfaPolicy: "required" }),
},
);
if (!response.ok) throw new Error(`update failed: ${response.status}`);{tenantId} / {id} is your tenant's id — the same one in your dashboard URL —
and the token must hold administrative authority over that tenant.
Administrator credentials required. These management calls use the
credential associated with an authorized Udibo dashboard session. A machine
credential issued to an application in your own tenant is not accepted on
/api/identity/…. Use the dashboard or an approved administrator workflow; keep
administrator credentials out of your application integration.
Last verified 2026-09-06.

