Skip to content

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 /security page: 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:

ValueMeaning
offNo one can newly enroll. A factor that is already enrolled is still challenged — see below.
optionalUsers may enroll themselves; enrolled users are challenged at sign-in. The default.
requiredEveryone 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.

MethodPathScope
POST/api/identity/{tenantId}/users/{id}/reset-mfaidentity:users:write
TypeScript
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:

MethodPathScope
PATCH/api/identity/tenants/{id}identity:tenants:write
TypeScript
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.