Documentation
ReferenceJavaScript SDKNamespacesauth.factors

auth.factors

Enrolling multi-factor methods and managing recovery codes.

Factors are what turn a sign-in into a two-step sign-in. This namespace is the management half — a signed-in user setting up or removing their own methods. The other half, the challenge that appears at sign-in afterwards, is auth.challenges.

Every call here except enable is sudo-protected under manage_mfa. That is deliberate: a stolen session must not be able to remove the second factor that would have stopped it.

list

list() → AuthMethod[]

The factors configured for the current user.

const { data: factors } = await nulvless.auth.factors.list();
idstringOptional

What unenroll takes.

typeMFAMethodRequired

totp, sms_otp, email_otp or recovery_codes.

namestringOptional

Whatever the user called it at enrolment — "Work phone", "1Password".

statusAuthFactorStatusRequired
configuredSet up and usable.
not_configuredAvailable on this environment, not set up.
created_atstringOptional

An empty result after sign-in is the signal to walk the user through enrolment, which is how you satisfy an environment that requires MFA.


enroll

enroll({ type, name, phone, nonce }) → FactorEnrollment

Begins setting up a factor. Nothing is active yet.

const { data } = await nulvless.auth.factors.enroll({
  type: 'totp',
  name: 'My authenticator',
  nonce: code,
});

// data.url → render as a QR code
// data.secret → manual entry
typeMFAMethodRequired
totpAn authenticator app. Returns a secret and a provisioning URL.
sms_otpCodes by SMS.
email_otpCodes by email.
recovery_codesThe printable fallback set.
namestringOptional

Shown in the user's factor list. Worth collecting — a person with two authenticators cannot tell them apart otherwise.

phonePhoneOptional

sms_otp only. The number that receives the codes — it can differ from the user's own phone. Defaults to the user's phone. The same number cannot be an SMS factor twice (mfa_phone_already_enrolled).

noncestringOptional

Sudo confirmation.

A user holds up to 10 authenticator apps and SMS factors combined (mfa_factors_limit_reached beyond that; the environment can lower it), and a single email factor and set of recovery codes.

Returns FactorEnrollment:

tokenstringRequired

Short-lived. Carry it to enable.

secretstringOptional

TOTP only. The shared secret, for users who type it in by hand.

urlstringOptional

TOTP only. A provisioning URL — render it as a QR code directly rather than assembling one from the secret.


enable

enable({ token, code }) → null

Activates a pending enrolment by proving the device works.

await nulvless.auth.factors.enable({
  token: data.token,
  code: firstCodeFromTheApp,
});
tokenstringRequired

From enroll.

codestringRequired

The first code the user's device produces.

Until this succeeds the factor does not exist. An abandoned enrolment leaves the account exactly as it was — there is no half-enabled state to clean up, and no risk of locking someone out with a device that was never verified.

The only call in this namespace that does not need sudo. It is already proving possession of the device, which is what sudo would have asked for.


unenroll

unenroll({ id, nonce }) → AuthMethod

Removes a factor. Returns the one removed.

const { data: factors } = await nulvless.auth.factors.list();
const totp = factors?.find((factor) => factor.type === 'totp');

if (totp?.id) await nulvless.auth.factors.unenroll({ id: totp.id, nonce: code });
idstringRequired

From list().

noncestringOptional

On an environment that requires MFA, removing the last factor leaves the account unable to complete a sign-in. Warn before the last one, or pair the removal with enrolling a replacement.


getRecoveryCodes

getRecoveryCodes({ nonce }) → { codes }

Returns the user's recovery codes.

const { data } = await nulvless.auth.factors.getRecoveryCodes({ nonce: code });
// data.codes → string[]
noncestringOptional

Sudo confirmation.

Show them once, at enrolment, and make the user acknowledge that they were saved. They are the only self-service way back in for someone who loses their device — the alternative is a support path where an admin removes the factor with auth.admin.users.removeFactor, and the platform will not second-guess that call.

A recovery code is submitted in the ordinary code field of verifyChallenge — there is no separate call for redeeming one.


deleteRecoveryCodes

deleteRecoveryCodes({ nonce }) → null

Invalidates the current set.

await nulvless.auth.factors.deleteRecoveryCodes({ nonce: code });
noncestringOptional

What a "regenerate my codes" button does before issuing a fresh set. On its own it removes a fallback rather than adding one, so it is rarely the whole interaction.

Next

On this page