Реалізація двофакторної аутентифікації (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 + сторінка виклику 1 день
Резервні коди + захист від повторення 0,5 дня
UI (React/Vue) + тести 1,5 дня

Всього: 4–5 робочих днів.