TausiDocs

Two-factor authentication

Users can protect their account with an authenticator app (Google Authenticator, 1Password, Authy, Microsoft Authenticator…). Sign-in then asks for a 6-digit code after a correct password. It's available in every project; users choose to turn it on.

How it works

  1. The user turns it on in your app: they confirm their password, scan a QR code, and enter a first code. They also get 10 one-time backup codes.
  2. When they sign in with their password, Tausi doesn't create a session yet. It answers with twoFactorRequired and a short-lived challenge.
  3. Your app asks for the code and sends it. A correct code (or an unused backup code) creates the session, as a normal sign-in would.
Rule
Time to enter the code5 minutes, then sign in again (TWO_FACTOR_CHALLENGE_EXPIRED).
Wrong codes5 per sign-in attempt. After 10 in a row the factor locks for 15 minutes.
Backup codes10, each usable once. Users can generate a new set (the old ones stop working).
NotificationsThe user is emailed when two-factor is turned on or off, or the backup codes change.
Social sign-inGoogle, Apple, GitHub and the others aren't asked for a code: those providers have their own two-factor.
Authenticator nameYour project's name, so users can tell your app apart in their authenticator.

Sign-in with a code

Users without two-factor sign in in one step, exactly as before. For users with it on, add a code step. In JavaScript and Node.js signIn() returns { twoFactorRequired: true }; Python, PHP and Flutter raise a TwoFactorRequired error, so existing code that only catches TausiError keeps working.

TSX
const result = await tausi.signIn({ email, password });
if (result.twoFactorRequired) {
  // Show a code field. The client keeps the pending sign-in for 5 minutes.
  return setStep("code");
}
// signed in: result.user, result.session

// When the user submits the code:
await tausi.verifyTwoFactor({ code });                           // 6-digit code from the app
await tausi.verifyTwoFactor({ code, method: "backup_code" });    // or one of their backup codes
Next.js cookie mode
Nothing extra to set up: the proxy route forwards the code step, and sets the session cookie once the code is right.

Turning it on and off

Add a security page to your app with these calls. The user has to be signed in, and each step that changes the setting asks for their password. Turning it on or off signs the current device in with a fresh token; the SDKs store it for you.

TSX
const { totpURI, backupCodes } = await tausi.setUpTwoFactor(password);
// Show totpURI as a QR code (e.g. the "qrcode" package) and the backup codes once.

await tausi.enableTwoFactor(codeFromApp);   // now it's on; the user is emailed

await tausi.regenerateBackupCodes(password);
await tausi.disableTwoFactor(password);
tausi.getState().user?.twoFactorEnabled;    // show the right button

Try it without writing code: the test app's account page (Dashboard → project → Quickstart → Open test app) has a working two-factor section.

When a user loses their phone

With a backup code they can sign in and set two-factor up again. Without one, turn it off for them, after checking it's really them. They're emailed, and the action is recorded in your project's logs.

  • Dashboard: Users → the user → Turn off two-factor (asks for your password).
  • Your backend, with the secret key:
resetPHP
await tausi.users.resetTwoFactor(userId);          // Node.js
backend.users.reset_two_factor(user_id)            # Python
$backend->resetTwoFactor($userId);                 // PHP
curl -X DELETE https://auth.tausi.app/api/v1/backend/users/$USER_ID/two-factor -H "Authorization: Bearer sk_…"

Over plain HTTP

httpHTTP
POST /api/v1/auth/sign-in/email          { "email": "…", "password": "…" }
→ 200 { "data": { "twoFactorRequired": true, "twoFactorToken": "…" } }

POST /api/v1/auth/two-factor/verify      { "method": "totp", "code": "123456", "twoFactorToken": "…" }
→ 200 { "data": { "user": {…}, "session": {…}, "sessionToken": "…" } }

POST /api/v1/auth/two-factor/setup       { "password": "…" }          (signed in)  → { totpURI, backupCodes }
POST /api/v1/auth/two-factor/enable      { "code": "123456" }                      → { enabled: true, sessionToken }
POST /api/v1/auth/two-factor/disable     { "password": "…" }                       → { enabled: false, sessionToken }
POST /api/v1/auth/two-factor/backup-codes { "password": "…" }                      → { backupCodes }

method is totp or backup_code. All error codes are on the HTTP API page.