Реализация двухфакторной аутентификации (2FA/TOTP) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация двухфакторной аутентификации (2FA/TOTP) на сайте
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация двухфакторной аутентификации (2FA/TOTP) на сайте

Двухфакторная аутентификация через TOTP (Time-based One-Time Password) — второй фактор в виде 6-значного кода из приложения-аутентификатора (Google Authenticator, Authy, 1Password и др.). Коды генерируются по алгоритму RFC 6238: HMAC-SHA1 от текущего времени и секретного ключа, обновляются каждые 30 секунд.

Стек

  • Алгоритм: TOTP (RFC 6238), HOTP (RFC 4226)
  • Библиотека PHP: pragmarx/google2fa или sonata-project/google-authenticator
  • QR-код: bacon/bacon-qr-code
  • Хранение: зашифрованный секретный ключ в БД

Схема включения 2FA

1. Пользователь нажимает "Включить 2FA"
2. Сервер генерирует секрет (base32, 160 бит)
3. Сервер отдаёт QR-код и резервные коды
4. Пользователь сканирует QR в аутентификаторе
5. Пользователь вводит первый код для подтверждения
6. Сервер сохраняет секрет как активный

Генерация секрета и QR-кода

composer require pragmarx/google2fa bacon/bacon-qr-code
use PragmaRX\Google2FA\Google2FA;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;

class TwoFactorSetupController extends Controller
{
    public function setup(Request $request): JsonResponse
    {
        $google2fa = new Google2FA();
        $secret = $google2fa->generateSecretKey(32);

        // Временно хранить в сессии до подтверждения
        session(['2fa_pending_secret' => $secret]);

        $otpauthUrl = $google2fa->getQRCodeUrl(
            config('app.name'),
            $request->user()->email,
            $secret
        );

        $renderer = new ImageRenderer(
            new RendererStyle(200),
            new SvgImageBackEnd()
        );
        $writer = new Writer($renderer);
        $qrSvg = $writer->writeString($otpauthUrl);

        return response()->json([
            'secret'   => $secret,
            'qr_code'  => base64_encode($qrSvg),
            'qr_uri'   => $otpauthUrl,
        ]);
    }
}

Подтверждение и активация

public function confirm(Request $request): JsonResponse
{
    $request->validate(['code' => 'required|digits:6']);

    $secret = session('2fa_pending_secret');
    $google2fa = new Google2FA();

    if (!$google2fa->verifyKey($secret, $request->code)) {
        return response()->json(['message' => 'Неверный код'], 422);
    }

    // Сохранить зашифрованный секрет
    $request->user()->update([
        'two_factor_secret'     => encrypt($secret),
        'two_factor_enabled_at' => now(),
    ]);

    // Сгенерировать резервные коды
    $recoveryCodes = $this->generateRecoveryCodes();
    $request->user()->update([
        'two_factor_recovery_codes' => encrypt(json_encode($recoveryCodes)),
    ]);

    session()->forget('2fa_pending_secret');

    return response()->json(['recovery_codes' => $recoveryCodes]);
}

private function generateRecoveryCodes(): array
{
    return collect(range(1, 8))->map(fn() =>
        Str::random(5) . '-' . Str::random(5)
    )->toArray();
}

Middleware для проверки 2FA

После базовой аутентификации пользователь с включённой 2FA должен пройти второй фактор:

class TwoFactorMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();

        if ($user && $user->two_factor_enabled_at && !session('2fa_verified')) {
            return redirect()->route('2fa.challenge');
        }

        return $next($request);
    }
}
class TwoFactorChallengeController extends Controller
{
    public function verify(Request $request): RedirectResponse
    {
        $request->validate(['code' => 'required|string']);
        $user = $request->user();
        $google2fa = new Google2FA();

        $secret = decrypt($user->two_factor_secret);
        $code = $request->code;

        // Проверить TOTP-код
        $validTotp = $google2fa->verifyKey($secret, $code, 1); // window=1 (±30 сек)

        // Если не TOTP — проверить резервный код
        if (!$validTotp) {
            $recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true);

            if (!in_array($code, $recoveryCodes, true)) {
                return back()->withErrors(['code' => 'Неверный код']);
            }

            // Использованный резервный код удалить
            $remaining = array_filter($recoveryCodes, fn($c) => $c !== $code);
            $user->update([
                'two_factor_recovery_codes' => encrypt(json_encode(array_values($remaining))),
            ]);
        }

        session(['2fa_verified' => true]);
        return redirect()->intended('/dashboard');
    }
}

Защита от повторного использования кода

TOTP-коды действуют 30 секунд. Чтобы исключить повторное использование одного кода:

// Хранить хэш последнего использованного кода
$lastUsed = Cache::get("2fa_last_used:{$user->id}");
$currentHash = hash('sha256', $secret . $code . floor(time() / 30));

if ($lastUsed === $currentHash) {
    return response()->json(['message' => 'Код уже был использован'], 422);
}

Cache::put("2fa_last_used:{$user->id}", $currentHash, 60);

Резервные коды

8 одноразовых кодов вида xxxxx-xxxxx. Показываются один раз при настройке — пользователь должен их сохранить. При использовании резервного кода — удалять из списка. Когда остаётся менее 2 кодов — уведомить пользователя о необходимости сгенерировать новые.

Сроки работ

Этап Время
Генерация секрета + QR-код 1 день
Подтверждение + сохранение 0.5 дня
Middleware + challenge-страница 1 день
Резервные коды + повторное использование 0.5 дня
UI (React/Vue) + тесты 1.5 дня

Итого: 4–5 рабочих дней.