Skip to content

Set up authenticator app codes

A two-step settings page with a QR code, backup codes shown once, the code check at sign-in, a lost phone, the lock, and how to test it on your phone.

AuthenticatorUpdated 26 October 20267 min read

Authenticator app codes are the six digits from Google Authenticator or a similar app, typed as a second step after the password. Nothing changes in your mobile app: the user scans a QR code from your settings page, and your server checks each code with one call. This guide builds that settings page, the sign-in step and the lost-phone path. It takes about an hour.

How it fits together

  1. Turn on: your server asks SignOps to start enrolment, your page shows the QR, the user scans it and types the first code, and your server gets 10 backup codes to show once.
  2. Sign in: after the password, your page asks for a code, and your server checks it.
  3. Lost phone: a backup code works in place of an app code. Your support team can remove the authenticator from the dashboard.

Every call is from your server with the secret key. The examples use the Node library (npm install @signops/node); PHP and Python have the same calls, and the API reference has cURL.

1. Turn on two-step sign-in

On your account security page, add a Turn on authenticator app button. It calls your server:

app.post("/api/2fa/start", requireUser, async (req, res) => {
  const { qr_svg, secret } = await signops.totp.enroll({
    user_id: req.user.id,        // your own user id
    label: req.user.email,       // shown in the authenticator app
  });
  res.json({ qr_svg, secret });
});

Show qr_svg on the page (it is an SVG, ready to put in the page), and under it the secret for users who type it in, in groups of four. Then ask for the first code:

app.post("/api/2fa/confirm", requireUser, async (req, res) => {
  const { backup_codes } = await signops.totp.confirm({ user_id: req.user.id, code: req.body.code });
  res.json({ backup_codes });   // show them once, with Download and Copy buttons
});

A wrong first code answers 400 invalid_code and the user tries again with the next one; wrong first codes count toward the lock too. Until the first code is right, the authenticator is not on, and a start that is never confirmed is cleared away on its own.

Show the 10 backup codes once, and say plainly: “Save these. Each one signs you in once if you lose your phone.” SignOps keeps only a keyed hash of each, so they cannot be shown again.

2. Ask for a code at sign-in

After the password is right, check whether the user has an authenticator (store a flag in your own users table when confirm succeeds, or call signops.totp.status(user_id)). If they do, show a code field instead of signing them in:

<input name="code" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit code or backup code" />
app.post("/api/2fa/check", async (req, res) => {
  const userId = req.session.pendingUserId;   // set after the password was right
  try {
    const r = await signops.totp.verify({ user_id: userId, code: req.body.code });
    if (!r.valid) return res.status(401).json({ left: r.failures_before_lock });
    if (r.method === "backup_code" && r.backup_codes_left <= 3) flash(req, "You have few backup codes left. Make new ones in settings.");
    startSession(res, userId);
  } catch (e) {
    if (e.code === "user_locked") {
      const lock = await signops.users.lock(userId);   // locked_until, for "try again in 2 minutes"
      return res.status(429).json({ locked_until: lock.locked_until });
    }
    throw e;
  }
});

The same call takes an app code or a backup code; method says which. Each code works once, so a code seen over someone’s shoulder cannot be used again.

3. The lock

Five wrong codes in 15 minutes lock the user for one minute, then two, doubling up to an hour. While locked, verify answers 429 user_locked with Retry-After, and your webhook gets user.locked. A right code clears the count.

Say so on the page (“Too many wrong codes. Try again in 2 minutes.”). If a real user is stuck, unlock them early with signops.users.unlock(user_id) or from the user’s page in the dashboard.

4. Settings the user will want

Button on your security page Call
Make new backup codes signops.totp.backupCodes(user_id): the old ones stop working
Turn off the authenticator signops.totp.remove(user_id), after asking for a current code
Move to a new phone Turn it off, then turn it on again with the new phone

5. A lost phone with no backup codes

Decide this before you need it. Common choices: the user proves who they are to your support team, which removes the authenticator under Users in the dashboard (or with DELETE /v1/users/:user_id/totp), or the user signs in with an email code plus a waiting period. The removal goes to your webhook as totp.removed.

6. Test it on your own phone

Before writing code, try the whole flow in the dashboard:

  1. Install Google Authenticator or Microsoft Authenticator on your phone.
  2. In the dashboard, open Authenticator, Setup, Try it here. Keep the user id try-user-1 and choose Start enrolment.
  3. In the authenticator app, tap +, Scan a QR code, and scan the screen. If the dashboard is open on the same phone, copy the key under the QR instead and choose Enter a setup key, Time based.
  4. Type the current code and choose Confirm. You get 10 backup codes.
  5. Type the next code and choose Check code: “Good code”. Type the same code again: it fails, because each code works once. Try a backup code, then five wrong codes to see the lock.

Change the user id to start over, or remove it from the user’s page under Users. Every check counts toward the month’s authenticator checks, so test by hand rather than in a loop.

Next

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
Which authenticator apps work?

Any app that reads TOTP QR codes, including Google Authenticator, Microsoft Authenticator, Authy, 1Password, Bitwarden and the iPhone's own Passwords app. Codes are 6 digits, every 30 seconds, SHA-1, as RFC 6238 sets out.

What name does the user see in their authenticator app?

Your app's name in SignOps and the label you send, such as Kirana Express (ananya@example.in). Pass issuer to show a different name. SignOps never appears.

The user's codes are always wrong. Why?

Almost always the phone's clock. SignOps accepts the code 30 seconds early or late. Ask the user to turn on automatic date and time, or use a backup code.

Can a user have two authenticator apps?

One authenticator per user. They can scan the same QR into two apps at enrolment, or keep the backup codes for when the phone is lost.

Send your first push today.

Free for 1,000 push devices, 100 approvals and 1,000 passkey sign-ins a month, and 500 email codes to try. No card until you pass a free amount.