Voyti
Two-Factor Authentication
Two-factor authentication is optional and pluggable: the core carries no 2FA code of its own, only the seams that let method packages hook into login. Install one or more method packages to activate 2FA.
After password login succeeds, 2FA checks if the user has an enabled method. If so, it holds the session pending 2FA verification, runs the method’s confirmation step (e.g., email code, TOTP entry), and verifies the result. On success, the login completes. Backup codes are auto-generated when enabling 2FA, allowing account recovery if the primary method is unavailable.
Mails a random six-digit code at the start of the confirmation step; needs only a configured mailer.
| Route name | Method | Path | Purpose |
|---|---|---|---|
voyti/user-two-factor-email |
GET |
settings/two-factor/email/ |
Email setup fragment |
voyti/user-two-factor-email-send-code |
POST |
settings/two-factor/email/send-code |
Send the email one-time code |
Time-based One-Time Password (TOTP) using an authenticator app like Google Authenticator, Authy, or Microsoft Authenticator. Users scan a QR code during setup to register the account, then enter time-based codes generated by their app during login.
| Route name | Method | Path | Purpose |
|---|---|---|---|
voyti/user-two-factor-totp |
GET |
settings/two-factor/totp/ |
TOTP setup (QR code) |
voyti/user-two-factor-totp-renew |
POST |
settings/two-factor/totp/renew |
Issue a new TOTP secret/QR |
Passwordless authentication using biometrics (fingerprint, face recognition) or hardware security keys. The credential is registered on the user's device and verified during login without requiring a typed code.
| Route name | Method | Path | Purpose |
|---|---|---|---|
voyti/user-two-factor-webauthn |
GET |
settings/two-factor/webauthn/ |
WebAuthn setup (registration ceremony) |
voyti/user-two-factor-webauthn-register |
POST |
settings/two-factor/webauthn/register |
Complete WebAuthn registration |
voyti/user-two-factor-webauthn-confirm |
GET |
confirm/webauthn |
WebAuthn login-confirmation fragment (guest-accessible) |
Pulled in automatically as a dependency by method packages, the base package supplies shared routes, backup codes, and database tables.
| Route name | Method | Path | Purpose |
|---|---|---|---|
voyti/session-confirm |
GET, POST |
confirm |
Two-factor login-confirmation step (guest-accessible mid-login) |
voyti/user-two-factor |
GET, POST |
settings/two-factor/ |
Two-factor status/entry point |
voyti/user-two-factor-enable |
POST |
settings/two-factor/enable |
Enable 2FA - shared by every code-based method’s code-entry form |
voyti/user-two-factor-disable |
POST |
settings/two-factor/disable/ |
Disable 2FA |
voyti/user-two-factor-disable-send-code |
POST |
settings/two-factor/disable/send-code |
Send the disable-2FA one-time code (for methods that deliver a code) |
voyti/user-two-factor-backup-codes |
GET |
settings/two-factor/backup-codes |
Display the user’s backup codes |
voyti/user-two-factor-regenerate-backup-codes |
POST |
settings/two-factor/backup-codes/regenerate |
Invalidate existing backup codes and generate a fresh set (requires re-verifying the current method) |
Configuration
// config/params.php
return [
'yiirocks/voyti' => [
'2fa' => [
'forcedPermissions' => ['voyti-admin'],
],
],
];[]TwoFactorAuthenticationEnforceMiddleware.Email method
// config/params.php
return [
'yiirocks/voyti' => [
'2fa' => [
'email' => [
'maxAttempts' => 3,
],
],
],
];6005Console Commands
The base package registers one console command under yiisoft/yii-console:
| Command | Description |
|---|---|
voyti:2fa:disable |
Disable two-factor authentication for a user, bypassing re-authentication |
php yii voyti:2fa:disable --email=user@example.com
php yii voyti:2fa:disable --username=johndoe
php yii voyti:2fa:disable --id=42Writing a method plugin
-
Register the method
Tag the provider class with
voyti.two-factor-methodinconfig/di.php. The registry collects tagged providers, keyed bygetName():// config/di.php use MyNamespace\MyTwoFactorMethod; return [ MyTwoFactorMethod::class => [ 'class' => MyTwoFactorMethod::class, 'tags' => ['voyti.two-factor-method'], ], ]; -
Contribute setup routes
Append routes to
yiirocks/voyti→2fa→methodRoutesinconfig/params.php. They’re spliced into the base package’ssettings/group, inheriting login guards and CSRF middleware:// config/params.php use MyNamespace\MyMethodController; use Yiisoft\Router\Route; return [ 'yiirocks/voyti' => [ '2fa' => [ 'methodRoutes' => [ Route::get('two-factor/my-method/') ->name('voyti/user-two-factor-my-method') ->action([MyMethodController::class, 'settings']), ], ], ], ];Route lists merge and append, so multiple method packages coexist without collision. Use the
voyti/user-two-factor-<name>convention; that’s whatgetSettingsUrl()generates. Client-collected methods (WebAuthn) register their guest-accessible confirmation fragment as a top-level route group usingVoytiRoutes::webMiddleware(). -
Implement the interface
TwoFactorMethodInterface(namespaceYiiRocks\Voyti\TwoFactor, provided by the base package) is the contract you must implement:Method Purpose getName()The exclusive name the method is stored under; also the registry key. isAvailable()Whether the backing library is installed. Unavailable methods are hidden and never chosen as the default - the graceful self-disable hook. isCodeBased()truefor a user-typed code (TOTP, email),falsefor a client-collected payload.requiresCodeDelivery()Whether a code must be delivered before the user can enter it (email) versus available on demand (TOTP, WebAuthn). Drives the disable flow’s “send a code first” pre-step generically. verify($user, $data)Validate the attempt. $datais['code' => ...]for code-based methods,['payload' => ...]for client-collected ones.getSettingsUrl($url)GET route for the method’s settings screen; the settings page also fetches it to lazy-load the setup fragment. getConfirmFragmentUrl($url)GET route for the login-confirmation fragment, or nullfor code-based methods (their code form is rendered inline).getReauthFragmentUrl($url)GET route for the re-authentication fragment used by sensitive actions (disable 2FA, regenerate backup codes), or nullfor code-based methods (their code is rendered inline). For client-collected methods like WebAuthn, this fragment runs the assertion ceremony.getButtonLabel($translator)Short label for the method-switch button. getEnabledWithMethodName($translator)Name shown in the “2FA is enabled with {method}” message. getErrorMessage()Translated error from the last failed verify(); empty on success.onAuthenticationStepStart($user)Runs when a login reaches this method’s confirmation step (e.g. email a fresh code); a no-op for methods with nothing to send. onDisable($user)Runs when 2FA is disabled entirely, to clear method-specific state.
Enrollment and storage
Your setup routes persist secrets and render the enrollment UI. The base package handles
finalization (verification, backup code generation) and teardown (disable, re-verify) generically -
your plugin doesn’t need to touch those. If your method needs a custom table, ship a model and
migration with ON DELETE CASCADE foreign keys.