beastbytes/yii-totp

Time-based One Time Password (TOTP) library for Yii3

dev-master 2025-04-07 20:51 UTC

This package is auto-updated.

Last update: 2025-04-07 20:53:29 UTC


README

BeastBytes Yii TOTP simplifies integrating Two-Factor Authentication (2FA) using TOTP (Time-based One-Time Password) into Yii3 applications.

Requirements

  • PHP 8.1 or higher.

Installation

composer require beastbytes/yii-totp

or add the following to the 'require' section composer.json:

"beastbytes/yii-totp": "<version-contraint>"

Usage

The application interacts with TotpService.

NOTE: Code examples show only the core functionality; they do not show dependency injections, support methods, ...

Configuration

The default configuration works with authenticator apps such as Google Authenticator, Aegis, etc.

Enable TOTP

// Totp Controller
public function enable(
    CurrentUser $currentUser,
    FormHydrator $formHydrator,
    ServerRequestInterface $request,
): ResponseInterface
{
    $formModel = new OtpForm($this->totpService);
    
    if ($formHydrator->populateFromPostAndValidate($formModel, $request)) {
        $this->redirct('ShowBackupCodes');
    }
    
    ['qrCode', 'secret'] = $this->totpService->createTotp($currentUser->getId());
    
    return $this->viewRenderer->render(
        'enable2faView',
        [
            'formModel' => $formModel,
            'qrCode' => $qrCode,
            'secret' => $secret,
        ]   
    );    
}
// enable TOTP View
<p>Either scan the QR Code or manually enter the <abbr title='Two&hyphen;Factor Authentication'>2FA</abbr> code into
    your 2FA app, then enter the <abbr title='One&hyphen;Time Password'> OTP</abbr> code generated by the app.</p>
<img src="<?=$qrCode?>" alt="QR Code" height="400px" width="400px" />
<div>2FA Code</div>
<div><?= $secret ?></div>
<?= $form
    ->post($url)
    ->csrf($csrf)
    ->open()
; ?>
<?= Field::text($formModel, 'code') ?>
<?= Field::submitButton('Verify') ?>
<?= $form->close() ?>

Verify TOTP

// Totp Controller
public function verify(
    CurrentUser $currentUser,
    FormHydrator $formHydrator,
    ServerRequestInterface $request,
): ResponseInterface
{
    $formModel = new OtpForm($this->totpService, true);
    
    if ($formHydrator->populateFromPostAndValidate($formModel, $request)) {
        $this->redirct('verified');
    }
    
    return $this->viewRenderer->render(
        'enable2faView',
        [
            'formModel' => $formModel,
        ]   
    );    
}
// Verify TOTP View
<?= $form
    ->post($url)
    ->csrf($csrf)
    ->open()
; ?>
<?= Field::text($formModel, 'code') ?>
<?= Field::submitButton('Verify') ?>
<?= $form->close() ?>

OTPForm

final class OtpForm extends FormModel
{
    private string $otpCode = '';
    
    public function __construct(
        private readonly TotpService $totpService, 
        private readonly bool $allowBackupCode = false
    )
    {        
    }

    public function getRules(): array
    {
        return [
            'otpCode' => [
                new Required(),
                new Regex(($this->allowBackupCode ? '/.+/' : '/\d{3}\s?\d{3}/')),
                new Callback(
                    callback: function (): Result {
                        $result = new Result();

                        if (!$this->totpService->verify(str_replace(' ', '', $this->otpCode))) {
                            $result->addError('Invalid Code');
                        }

                        return $result;
                    },
                ),
            ]
        ];
    }
}