Two-Step Sign-In
A person with two-step sign-in on must confirm a code from their authenticator app — Google Authenticator, Authy, 1Password — after their first factor, before they get a session. It is optional per user and off by default.
How it changes sign-in
Every sign-in method — email and password, email code, phone, Google, Apple — is unchanged for people without two-step sign-in on. For someone who has it on, the same call throws MfaRequiredException / MfaRequiredError instead of returning a session:
try {
final user = await Koolbase.auth.login(
email: '[email protected]',
password: 'her-password',
);
} on MfaRequiredException catch (e) {
// Ask for the code, then:
final user = await Koolbase.auth.verifyMfa(
challengeToken: e.challengeToken,
code: '123456',
);
}try {
const session = await Koolbase.auth.login({ email: '[email protected]', password: 'her-password' });
} catch (err) {
if (err instanceof MfaRequiredError) {
const session = await Koolbase.auth.verifyMfa({
challengeToken: err.challengeToken,
code: '123456',
});
}
}The challenge token lasts five minutes and allows three attempts. A wrong code returns otp_invalid; three wrong codes exhaust the challenge and the person must sign in again. If they use a recovery code instead, verifyRecoveryCode does the same thing and also returns how many remain — so the app can prompt for a new set when they run out.
For every sign-in method
Turning it on
Enrolment needs a sign-in within the last ten minutes — so a session found on an unattended device cannot quietly add its own authenticator.
// 1. Start — returns the setup link and key
final enrolment = await Koolbase.auth.enrollMfa();
// show enrolment.otpauthUri as a QR code
// show enrolment.secret for people who type it in
// 2. Confirm — the first code from the app
final recoveryCodes = await Koolbase.auth.confirmMfaEnrollment('123456');
// Save these — they are shown once onlyconst { otpauthUri, secret } = await Koolbase.auth.enrollMfa();
// show the QR code
const codes = await Koolbase.auth.confirmMfaEnrollment('123456');
// save the recovery codesConfirming turns MFA on, returns ten recovery codes and signs every other device out. Sessions created before MFA was on would otherwise keep working; they are revoked so MFA protects the account from the moment it is enabled.
Recovery codes
Ten codes are generated when MFA is turned on. Each works once, in place of a TOTP code, for someone who has lost access to their authenticator app. Regenerating replaces the whole set; the old ones stop working immediately. To regenerate, a second factor confirmed in the last ten minutes is required — use stepUpMfa first.
No email bypass
Changing MFA needs a recent second factor
Turning MFA off or regenerating recovery codes needs the second factor confirmed in the last ten minutes — not just a session. A session found on an unattended device cannot make these changes. Call stepUpMfa first with a current authenticator code or a recovery code, then make the change:
await Koolbase.auth.stepUpMfa(code: '123456');
// or: await Koolbase.auth.stepUpMfa(recoveryCode: 'abcde-fghij');
await Koolbase.auth.disableMfa();
// or: final codes = await Koolbase.auth.regenerateRecoveryCodes();await Koolbase.auth.stepUpMfa({ code: '123456' });
await Koolbase.auth.disableMfa();Admin reset
For someone who has lost both their authenticator and their recovery codes: a project admin can reset their MFA from the Users page, or through the API:
POST /v1/projects/{project_id}/users/{user_id}/mfa/reset
Authorization: Bearer <dashboard session>This removes the authenticator and recovery codes, signs the person out on every device, and emails them. They can sign in with their first factor and enrol again. An admin never sees a secret or a code — reset means removal and re-enrolment, not impersonation.
Only do this once you're sure
Errors
mfa_required— first factor passed; a second is needed. The exception carries the challenge tokenotp_invalid— wrong code; attempts remainotp_max_attempts— three wrong attempts; sign in againotp_expired— the challenge expired or was already usedrecent_auth_required— starting enrolment needs a sign-in within ten minutesrecent_mfa_required— turning MFA off or regenerating codes needsstepUpMfafirstmfa_already_enabled— an authenticator is already on for this accountmfa_enrollment_not_found— no enrolment in progress, or it expired after 15 minutesmfa_not_enabled— the account has no authenticator to change
Requirements
koolbase_flutter12.8.0 or later@koolbase/js/@koolbase/react-native12.2.0 or later