2FA в Python: пошаговая реализация для Django (django-otp) и Flask (pyotp) | Готовый код | AdminWiki
Timeweb Cloud — сервера, Kubernetes, S3, Terraform. Лучшие цены IaaS.
Попробовать

2FA в Python: пошаговая реализация для Django (django-otp) и Flask (pyotp) | Готовый код

24 мая 2026 8 мин. чтения
Содержание статьи

Внедрение двухфакторной аутентификации (2FA) критически важно для защиты веб-приложений от компрометации учетных записей. Это практическое руководство предоставляет готовые, проверенные решения для двух популярных Python-фреймворков. Вы получите полный код для Django с использованием django-otp и для Flask с библиотекой pyotp, включая генерацию QR-кодов, проверку TOTP-токенов и обработку критических сценариев восстановления доступа. Инструкции ориентированы на продакшн-среду с акцентом на безопасное хранение секретов.

Что нужно знать перед внедрением 2FA: принципы TOTP и выбор подхода

Двухфакторная аутентификация добавляет второй, независимый от пароля, этап проверки личности пользователя. Наиболее распространенный и совместимый с популярными приложениями вроде Google Authenticator или Authy метод - Time-based One-Time Password (TOTP). Его работа основана на открытом стандарте RFC 6238.

Как работает TOTP и почему это стандарт для Google Authenticator

TOTP генерирует одноразовые коды, зависящие от общего секретного ключа и текущего времени. Процесс включает три шага:

  1. Сервер генерирует криптографически случайный секретный ключ (например, строку base32).
  2. Этот ключ передается мобильному приложению пользователя, обычно через сканирование QR-кода, содержащего специальный URI (otpauth://totp/...).
  3. И сервер, и мобильное приложение, используя один алгоритм (HMAC-SHA1) и синхронизированное время, независимо вычисляют 6-значный код, который меняется каждые 30 секунд.

Стандартизация протокола гарантирует совместимость с десятками приложений-аутентификаторов, что делает TOTP универсальным выбором.

Django-otp или pyotp: сравниваем готовое решение и кастомную сборку

Выбор инструмента зависит от фреймворка и требуемого уровня контроля.

КритерийDjango-otp (для Django)PyOTP (для Flask или кастомных решений)
Уровень интеграцииГлубокая. Плагины для админки, сессий, middleware.Минимальная. Предоставляет только базовые функции генерации и проверки кодов.
Скорость внедренияВысокая. Многое работает "из коробки".Средняя. Требуется самостоятельно реализовать логику хранения, активации и проверки.
ГибкостьОграничена архитектурой плагинов.Максимальная. Полный контроль над каждым этапом.
Сложность поддержкиНизкая. Обновления и исправления от сообщества.Высокая. Вы отвечаете за всю реализацию.

Вывод: Для типичных Django-проектов django-otp - оптимальный путь. Для Flask или если нужен полный контроль над процессом, используйте pyotp. Для комплексного внедрения 2FA в корпоративной среде рекомендуем ознакомиться с нашим полным руководством по настройке 2FA для DevOps и сисадминов.

Пошаговая реализация 2FA в Django с django-otp (готовый код)

Это полное решение для интеграции TOTP-аутентификации в проект на Django.

Установка, настройка и подключение django-otp к проекту

Установите необходимые пакеты:

pip install django-otp qrcode

В файле settings.py добавьте приложения и middleware:

INSTALLED_APPS = [
    ...
    'django_otp',
    'django_otp.plugins.otp_totp',  # Поддержка TOTP
    'django_otp.plugins.otp_static', # Для резервных кодов (опционально)
]

MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django_otp.middleware.OTPMiddleware',  # Обязательно после AuthenticationMiddleware
    ...
]

Выполните миграции: python manage.py migrate.

Генерация секрета и QR-кода для привязки к Google Authenticator

Создайте view для активации 2FA. Пример в views.py:

import qrcode
import qrcode.image.svg
from io import BytesIO
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django_otp.plugins.otp_totp.models import TOTPDevice

@login_required
def activate_2fa(request):
    # Удаляем старое устройство, если было
    TOTPDevice.objects.filter(user=request.user, name='default').delete()
    
    # Создаем новое TOTP-устройство для пользователя
    device = TOTPDevice.objects.create(user=request.user, name='default', confirmed=False)
    
    # Генерируем конфигурационный URI для аутентификатора
    secret_key = device.config_url().split('secret=')[1].split('&')[0]
    provisioning_uri = device.config_url
    
    # Создаем QR-код
    img = qrcode.make(provisioning_uri)
    buffer = BytesIO()
    img.save(buffer, format='PNG')
    qr_code_b64 = base64.b64encode(buffer.getvalue()).decode()
    
    context = {
        'qr_code': qr_code_b64,
        'secret_key': secret_key,
        'provisioning_uri': provisioning_uri
    }
    return render(request, 'enable_2fa.html', context)

В шаблоне enable_2fa.html отобразите QR-код:

<img src="data:image/png;base64,{{ qr_code }}" alt="QR Code for 2FA">
<p>Сканируйте этот QR-код в приложении Google Authenticator, Authy или аналоге.</p>
<p>Или введите ключ вручную: <code>{{ secret_key }}</code></p>

Встраивание проверки кода в процесс аутентификации Django

Создайте view для проверки введенного кода:

from django.contrib.auth import login
from django.shortcuts import render, redirect
from django_otp.plugins.otp_totp.models import TOTPDevice

def verify_2fa(request):
    if request.method == 'POST':
        token = request.POST.get('token')
        user_id = request.session.get('user_id_for_2fa')
        
        if user_id:
            try:
                user = User.objects.get(id=user_id)
                device = TOTPDevice.objects.get(user=user, name='default')
                
                if device.verify_token(token):
                    device.confirmed = True
                    device.save()
                    login(request, user)
                    # Очищаем временную сессию
                    request.session.pop('user_id_for_2fa', None)
                    return redirect('home')
                else:
                    return render(request, 'verify_2fa.html', {'error': 'Неверный код'})
            except (User.DoesNotExist, TOTPDevice.DoesNotExist):
                pass
    return render(request, 'verify_2fa.html')

Модифицируйте ваш view входа в систему. После успешной проверки пароля, если у пользователя включена 2FA, сохраните его ID в сессии и перенаправьте на страницу ввода TOTP-кода:

def custom_login(request):
    if request.method == 'POST':
        # ... проверка логина и пароля ...
        if user is not None:
            # Проверяем, активировал ли пользователь 2FA
            if TOTPDevice.objects.filter(user=user, confirmed=True).exists():
                request.session['user_id_for_2fa'] = user.id
                return redirect('verify_2fa')
            else:
                login(request, user)
                return redirect('home')
    ...

Пошаговая реализация 2FA в Flask с библиотекой pyotp (готовый код)

Для Flask-приложений реализация требует больше ручной работы, но дает полный контроль.

Структура данных и настройка зависимостей для Flask

Установите зависимости: pip install pyotp qrcode. Расширьте модель пользователя (на примере Flask-SQLAlchemy):

from flask_sqlalchemy import SQLAlchemy
import pyotp

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    # Поля для 2FA
    otp_secret = db.Column(db.String(32))  # Зашифрованный секрет
    is_2fa_enabled = db.Column(db.Boolean, default=False)
    
    def get_totp(self):
        # Функция для получения объекта TOTP, должна расшифровывать otp_secret
        if self.otp_secret:
            decrypted_secret = decrypt_secret(self.otp_secret)  # Ваша функция дешифрования
            return pyotp.TOTP(decrypted_secret)
        return None

Важно: Поле otp_secret должно храниться в зашифрованном виде. Открытый секрет в БД - критическая уязвимость.

Создание эндпоинтов для активации и проверки 2FA

Эндпоинт для включения 2FA и генерации QR-кода:

from flask import render_template, session, redirect, url_for
import pyotp
import qrcode
import base64
from io import BytesIO

@app.route('/enable-2fa', methods=['GET', 'POST'])
@login_required
def enable_2fa():
    if request.method == 'POST':
        # Генерируем новый случайный секрет
        secret = pyotp.random_base32()
        # Шифруем и сохраняем
        current_user.otp_secret = encrypt_secret(secret)  # Ваша функция шифрования
        current_user.is_2fa_enabled = True
        db.session.commit()
        
        # Создаем TOTP объект и URI для аутентификатора
        totp = pyotp.TOTP(secret)
        provisioning_uri = totp.provisioning_uri(name=current_user.username, issuer_name="Your App Name")
        
        # Генерируем QR-код в памяти
        img = qrcode.make(provisioning_uri)
        buffer = BytesIO()
        img.save(buffer, format="PNG")
        qr_code_b64 = base64.b64encode(buffer.getvalue()).decode()
        
        return render_template('enable_2fa.html', qr_code=qr_code_b64, secret=secret)
    return render_template('enable_2fa.html')

Эндпоинт для проверки кода при логине:

@app.route('/verify-2fa', methods=['POST'])
def verify_2fa():
    token = request.form.get('token')
    user_id = session.get('pending_user_id')
    
    if user_id:
        user = User.query.get(user_id)
        if user and user.is_2fa_enabled:
            totp = user.get_totp()
            if totp and totp.verify(token, valid_window=1):  # valid_window допускает рассинхронизацию
                session.pop('pending_user_id', None)
                login_user(user)  # Используйте вашу функцию логина
                return redirect(url_for('dashboard'))
    return render_template('verify_2fa.html', error="Неверный код или сессия истекла")

Интеграция двухэтапной проверки в существующий процесс логина Flask

Модифицируйте ваш эндпоинт входа:

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    user = User.query.filter_by(username=username).first()
    
    if user and check_password_hash(user.password_hash, password):
        if user.is_2fa_enabled:
            # Если 2FA включена, сохраняем user.id в сессии и перенаправляем на ввод TOTP
            session['pending_user_id'] = user.id
            return redirect(url_for('show_verify_2fa_page'))
        else:
            login_user(user)
            return redirect(url_for('dashboard'))
    return render_template('login.html', error="Неверные учетные данные")

Критически важные шаги для продакшн-безопасности

Учебные примеры часто игнорируют производственные требования, что приводит к уязвимостям или блокировке пользователей.

Где и как хранить секретные ключи: избегаем фатальных ошибок

НЕПРАВИЛЬНО: Хранить секрет в виде обычного текста в поле модели БД, хардкодить в settings.py или конфигурационных файлах.

ПРАВИЛЬНО: Использовать шифрование на уровне приложения с ключом, хранящимся в переменных окружения или в специализированном сервисе.

Пример функции шифрования/дешифрования с использованием cryptography:

from cryptography.fernet import Fernet
import os

# Ключ шифрования должен быть в переменной окружения, например, ENCRYPTION_KEY
def encrypt_secret(plaintext_secret):
    key = os.environ.get('ENCRYPTION_KEY').encode()
    cipher_suite = Fernet(key)
    encrypted_text = cipher_suite.encrypt(plaintext_secret.encode())
    return encrypted_text.decode()

def decrypt_secret(encrypted_secret):
    key = os.environ.get('ENCRYPTION_KEY').encode()
    cipher_suite = Fernet(key)
    decrypted_text = cipher_suite.decrypt(encrypted_secret.encode())
    return decrypted_text.decode()

В продакшн-среде рассмотрите использование выделенных сервисов для хранения секретов: HashiCorp Vault, AWS Secrets Manager или Azure Key Vault. Для управления множеством API-ключей, включая ключи к нейросетям, может быть полезен агрегатор AiTunnel, который также решает задачу безопасного хранения и ротации токенов.

Генерация и выдача резервных кодов для восстановления доступа

Резервные (backup) коды решают проблему потери или поломки устройства с аутентификатором. Алгоритм:

  1. Сгенерировать 10-16 случайных одноразовых кодов при активации 2FA.
  2. Хэшировать каждый код (например, с помощью bcrypt) перед сохранением в БД.
  3. Показать коды пользователю единственный раз с требованием их сохранения.
  4. При использовании кода удалять его хэш из БД.

Пример генерации:

import secrets
import bcrypt

def generate_backup_codes(count=10):
    codes = []
    hashed_codes = []
    for _ in range(count):
        code = secrets.token_urlsafe(8).replace('_', '').replace('-', '')[:8].upper()
        codes.append(code)
        hashed = bcrypt.hashpw(code.encode(), bcrypt.gensalt()).decode()
        hashed_codes.append(hashed)
    return codes, hashed_codes

Выдавайте коды в виде файла для скачивания (PDF/TXT), а не просто на странице, чтобы снизить риск их перехвата.

Обработка сценариев сбоя: потеря устройства и рассинхронизация времени

Потеря устройства: Реализуйте безопасный процесс сброса 2FA. Например, через подтверждение по дополнительному email, ответ на заранее заданный секретный вопрос или верификацию через службу поддержки с аудитом действий. Никогда не позволяйте сбрасывать 2FA только по логину и паролю.

Рассинхронизация времени: Если часы на сервере и телефоне пользователя расходятся, коды не будут приниматься. Библиотека pyotp и django-otp позволяют установить допустимое отклонение (valid_window). Значение 1 означает допуск в ±30 секунд (один временной интервал). В крайних случаях пользователь может вручную синхронизировать время в своем аутентификаторе. Подробнее о выборе и сравнении методов 2FA читайте в нашем материале о сравнении TOTP, Push, U2F и WebAuthn для корпоративных систем.

Тестирование и финальная проверка работоспособности

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

Чек-лист: проверка интеграции с Google Authenticator и Authy

  1. Создайте тестового пользователя и войдите в систему.
  2. Перейдите на страницу активации 2FA и отсканируйте QR-код в двух разных приложениях (например, Google Authenticator и Authy). Убедитесь, что аккаунт добавляется.
  3. Введите текущий 6-значный код из приложения в форму проверки. Доступ должен быть предоставлен.
  4. Подождите 30 секунд, пока код обновится, и повторите проверку с новым кодом.
  5. Проверьте работу резервных кодов: введите один из них вместо TOTP-кода. Код должен быть принят и отмечен как использованный.
  6. Протестируйте сценарий, когда 2FA отключена у пользователя - вход должен происходить только по паролю.

Если вы ищете альтернативу Google Authenticator, рассмотрите автономные решения с открытым исходным кодом, о которых мы писали в статье "Альтернативы Google Authenticator".

Актуальные версии ПО и ссылки на документацию

Представленные примеры кода протестированы и работают со следующими версиями (на май 2026 года):

  • Python: 3.8, 3.9, 3.10, 3.11
  • Django: 3.2 LTS, 4.x
  • django-otp: 1.x
  • Flask: 2.x
  • pyotp: 2.x

Всегда проверяйте актуальность документации на официальных ресурсах:

Перед внедрением в критически важные системы, такие как Git-репозитории, ознакомьтесь со специализированным руководством по защите Git-репозиториев через 2FA.

Поделиться:
Сохранить гайд? В закладки браузера