Skip to content

Two-factor authentication (TOTP)

Password logins can be protected with a second factor: a six-digit code from an authenticator app (Microsoft Authenticator, Google Authenticator, 1Password, …), as defined in RFC 6238. Users who log in with single sign-on get their second factor from the identity provider (e.g. ADFS) and are not asked again.

app/Config/Auth.php: TOTP becomes the second step of the login.

use Anvil\Auth\Mfa\TotpAction;
public array $actions = [
'register' => null,
'login' => TotpAction::class,
];

Run the migrations (php spark migrate --all) for the table anvil_mfa.

The step only applies to users who set up TOTP; all others log in with their password as before, unless TOTP is required.

The code is asked on Anvil’s view auth/totp. Like the login view it extends the layout of the application’s Config\Auth::$views['layout'] (read via Anvil\Support\ShieldConfig::auth()).

On the profile page, Two-factor authentication → Set up (activating and turning it off need the current password):

  1. Scan the QR code with the authenticator app (or enter the key).
  2. Enter the code shown by the app to activate it.
  3. Save the ten recovery codes. Each works once instead of a code, e.g. when the phone is lost. They are shown only once.

At the next login, after the password, Anvil asks for the code. Turning it off needs a current code.

For all panels:

app/Config/Anvil.php
public bool $mfaRequired = true;

or for single panels:

final class AdminPanel extends Panel
{
public function requiresMfa(): bool
{
return true;
}
}

Password users without TOTP are sent to their profile and can only use the setup until it is done; they cannot turn TOTP off. Single sign-on users are not affected.

Users who lost their device and their recovery codes: in the user administration, Reset two-factor authentication (permission users.update) turns TOTP off. With TOTP required the user sets it up again at the next login. Check the identity of the user before resetting (e.g. by phone, in person).

  • Codes are checked with a clock difference of ±30 seconds; every code works once (replay protection), also within its 30 seconds.
  • After five wrong codes the pending login ends and the password must be entered again. After ten wrong codes of an account (over all logins, also when turning TOTP off) its second factor is locked for 15 minutes (auth.mfa.locked).
  • Throttle the login routes of Shield as well, see Installation.
  • Remember me does not apply to users with TOTP: they enter the code at every login. Setting up, turning off or resetting TOTP and changing the password end all remember-me logins of the user.
  • The TOTP secret is encrypted (libsodium secretbox) with a key derived from encryption.key; recovery codes are stored as keyed hashes only. The secret is shown only during the setup and never leaves the server otherwise: the QR code is created on the server.
  • Rotating encryption.key makes the stored secrets unreadable: users must set up TOTP again (administrators reset it).
Event Context
auth.mfa.enabled, auth.mfa.disabled reason of disabling: user or reset by administrator
auth.mfa.passed with: totp or recovery, IP
auth.mfa.failed attempt number, IP
auth.mfa.recovery_code_used remaining codes
auth.mfa.locked minutes of the lock (after ten wrong codes)