Гид по технологиям

Аутентификация в Flask с помощью JWT

6 min read Безопасность Обновлено 04 Jan 2026
Flask + JWT — руководство по аутентификации
Flask + JWT — руководство по аутентификации

Простая и надёжная аутентификация в Flask реализуется с помощью JSON Web Tokens (JWT). В этой статье показано, как настроить проект Flask с MongoDB, зарегистрировать пользователей, вход (login), защищённые маршруты и как улучшить безопасность: истечение срока токена, безопасные cookie, отзыв токенов и лучшие практики для продакшена.

Зачем использовать токены и что такое JWT

Сломанная аутентификация остаётся одной из серьёзных уязвимостей у веб-приложений и API. Токен‑базированная аутентификация использует зашифрованную строку (токен) для проверки и авторизации доступа к ресурсам. JWT — компактный, самодостаточный формат токена, который безопасно переносит удостоверения между клиентом и сервером.

Ноутбук на сером столе, экран показывает текстовый редактор с двумя колонками кода.

Ключевые свойства JWT:

  • Состоит из трёх частей: header, payload, signature.
  • Payload содержит claims — полезные данные: user_id, роли, срок действия.
  • Подпись гарантирует целостность токена и защищает от подделки.

Пример: закодированный JWT слева и декодированный токен с выделением header, payload и signature справа.

Факты

  • JWT — текстовый формат, обычно кодируется Base64URL.
  • Стандарт определён в RFC 7519.

Важно: JWT сам по себе не шифрует данные, он их подписывает. Не храните в payload чувствительные данные без дополнительного шифрования.

Подготовка проекта Flask и MongoDB

Шаги, которые описаны ниже, помогут настроить окружение и зависимости.

Создайте директорию проекта и виртуальное окружение:

mkdir flask-project  
cd flask-project

Установите virtualenv и создайте окружение:

virtualenv venv

Активируйте виртуальное окружение:

# Unix or MacOS:   
source venv/bin/activate  
  
# Windows:   
.\venv\Scripts\activate

В корне создайте requirements.txt со следующими зависимостями:

flask  
pyjwt  
python-dotenv  
pymongo  
bcrypt

Установите зависимости:

pip install -r requirements.txt

Создайте MongoDB (локально или в облаке MongoDB Atlas), затем добавьте строку подключения в файл .env:

MONGO_URI=""

Пример модуля подключения к базе (utils/db.py):

from pymongo import MongoClient  
  
def connect_to_mongodb(mongo_uri):  
    client = MongoClient(mongo_uri)  
    db = client.get_database("users")  
    return db

Описание: функция создаёт клиент MongoDB и возвращает объект базы данных. Если коллекция ещё не создана, MongoDB создаст её при первой вставке.

Создайте основной файл приложения app.py:

from flask import Flask  
from routes.user_auth import register_routes  
from utils.db import connect_to_mongodb  
import os  
from dotenv import load_dotenv  
  
app = Flask(__name__)  
load_dotenv()  
  
mongo_uri = os.getenv('MONGO_URI')  
db = connect_to_mongodb(mongo_uri)  
  
register_routes(app, db)  
  
if __name__ == '__main__':  
    app.run(debug=True)

Модель пользователя

Создайте модель пользователя (models/user_model.py):

from pymongo.collection import Collection  
from bson.objectid import ObjectId  
  
class User:  
    def __init__(self, collection: Collection, username: str, password: str):  
        self.collection = collection  
        self.username = username  
        self.password = password  
    def save(self):  
        user_data = {  
            'username': self.username,  
            'password': self.password  
        }  
        result = self.collection.insert_one(user_data)  
        return str(result.inserted_id)  
  
    @staticmethod  
    def find_by_id(collection: Collection, user_id: str):  
        return collection.find_one({'_id': ObjectId(user_id)})  
  
    @staticmethod  
    def find_by_username(collection: Collection, username: str):  
        return collection.find_one({'username': username})

Модель предоставляет базовые операции: сохранение и поиск пользователя по id или username.

Маршруты аутентификации (routes/user_auth.py)

Ниже приведён минимальный вариант маршрутов регистрации, логина и защищённого доступа. Код сохранён из примера проекта — он работает, но требует усиления для продакшена.

import jwt  
from functools import wraps  
from flask import jsonify, request, make_response  
from models.user_model import User  
import bcrypt  
import os  
  
  
def register_routes(app, db):  
    collection = db.users  
    app.config['SECRET_KEY'] = os.urandom(24)  
  
    @app.route('/api/register', methods=['POST'])  
    def register():  
          
        username = request.json.get('username')  
        password = request.json.get('password')  
          
        existing_user = User.find_by_username(collection, username)  
        if existing_user:  
            return jsonify({'message': 'Username already exists!'})  
        
        hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())  
        new_user = User(collection, username, hashed_password.decode('utf-8'))  
        user_id = new_user.save()  
  
        return jsonify({'message': 'User registered successfully!', 'user_id': user_id})

Добавьте маршрут логина:

    @app.route('/api/login', methods=['POST'])  
    def login():  
        username = request.json.get('username')  
        password = request.json.get('password')  
        user = User.find_by_username(collection, username)  
        if user:  
            if bcrypt.checkpw(password.encode('utf-8'), user['password'].encode('utf-8')):  
                token = jwt.encode({'user_id': str(user['_id'])}, app.config['SECRET_KEY'], algorithm='HS256')  
            
                response = make_response(jsonify({'message': 'Login successful!'}))  
                response.set_cookie('token', token)  
                return response  
  
        return jsonify({'message': 'Invalid username or password'})

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

    def token_required(f):  
        @wraps(f)  
        def decorated(*args, **kwargs):  
            token = request.cookies.get('token')  
  
            if not token:  
                return jsonify({'message': 'Token is missing!'}) , 401  
  
            try:  
                data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])  
                current_user = User.find_by_id(collection, data['user_id'])  
            except jwt.ExpiredSignatureError:  
                return jsonify({'message': 'Token has expired!'}) , 401  
            except jwt.InvalidTokenError:  
                return jsonify({'message': 'Invalid token!'}) , 401  
  
            return f(current_user, *args, **kwargs)  
  
        return decorated

И защищённый маршрут для получения списка пользователей:

   @app.route('/api/users', methods=['GET'])  
    @token_required  
    def get_users(current_user):  
        users = list(collection.find({}, {'_id': 0}))  
        return jsonify(users)

Запустите сервер разработки:

flask run

Тестирование: используйте Postman или curl для отправки POST на /api/register и /api/login, затем GET на /api/users с кукой token.

Улучшения и практические рекомендации для продакшена

Приведённый выше код работает, но в реальном проекте требуется дополнительная защита. Ниже — список улучшений, которые стоит применить.

  1. Храните SECRET_KEY в окружении, а не генерируйте при старте приложения. Генерация новой ключа каждый запуск делает все токены недействительными при рестарте.

Пример в .env:

SECRET_KEY=""

И в app.py:

app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
  1. Добавьте срок жизни токена (exp) и используйте refresh tokens. Пример генерации токена с истечением:
import datetime

def create_access_token(user_id, secret, expires_minutes=15):
    payload = {
        'user_id': str(user_id),
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=expires_minutes)
    }
    return jwt.encode(payload, secret, algorithm='HS256')
  1. Безопасные cookie: при установке куки укажите HttpOnly, Secure и SameSite для защиты от XSS/CSRF:
response.set_cookie('token', token, httponly=True, secure=True, samesite='Strict')
  1. Реализация механизма отзыва токенов (blacklist) или хранение идентификатора сессии в БД. Если требуется принудительный разлогин, заносите jti (уникальный идентификатор токена) в таблицу отозванных токенов.

  2. Ограничьте количество попыток входа (rate limiting) и используйте капчу при необходимости.

  3. Всегда используйте HTTPS в продакшене.

  4. Минимизируйте данные в payload. Не храните пароли, секреты или PII (личные данные) в токене.

  5. Логи безопасности: фиксируйте неудачные попытки входа, истечения токенов, и массовые ошибки аутентификации.

  6. При хранении паролей используйте bcrypt с достаточным work factor (cost). Текущая библиотека bcrypt в Python делает это правильно, но настройте раундов по требованиям инфраструктуры.

  7. CORS: ограничьте домены, которые могут обращаться к API.

Когда JWT может не подойти: контрпримеры

  • Очень короткие сессии с частым изменением прав доступа. Потому что JWT сам по себе — статичный токен до истечения срока; смена прав потребует отзыв токена.
  • Необходимость незамедлительной инвалидизации токенов без централизованного хранилища отзыва. В таких случаях лучше использовать серверные сессии.
  • Ограничения по размеру токена при передаче по URL или в заголовках.

Альтернативы: серверные сессии (сессии в Redis), OAuth 2.0 (для сторонних авторизаций), API keys (для сервисов), mutual TLS (для высокобезопасных сервисов).

Методология внедрения (мини‑метод)

  1. Разработать минимально рабочую реализацию (MVP) с JWT и безопасными cookie.
  2. Добавить срок жизни токена и refresh tokens.
  3. Внедрить хранение jti в БД для возможности отзыва.
  4. Провести нагрузочное и безопасностное тестирование.
  5. Откат и улучшения: мониторинг, логирование, алерты.

Роли и чеклисты

Разработчик:

  • Хранит SECRET_KEY в окружении.
  • Добавил exp и jti в payload.
  • Установил HttpOnly/Secure/SameSite для cookie.
  • Использует bcrypt для хэширования пароля.

DevOps / SRE:

  • Включил HTTPS и HSTS.
  • Настроил резервное копирование базы данных.
  • Обеспечил мониторинг аутентификаций и метрик ошибок.

Команда безопасности:

  • Провела аудит токенов и проверила риски XSS/CSRF.
  • Настроила политику отказа доступа и процедуру отзыва токенов.

Примеры тест-кейсов и критерии приёмки

Критерии приёмки:

  • Регистрация нового пользователя возвращает user_id.
  • Успешный логин отдаёт токен и устанавливает cookie.
  • Защищённый маршрут /api/users доступен только с действительным токеном.
  • Токен с истёкшим exp отказывается с 401.
  • Попытки входа с неверным паролем не раскрывают, существует ли пользователь.

Тест-кейсы (кратко):

  1. Регистрация: POST /api/register с уникальным username — ожидается 200 и user_id.
  2. Логин: POST /api/login с правильными данными — ожидается кука token.
  3. Доступ защищённого ресурса: GET /api/users с кукой token — ожидается список пользователей.
  4. Просроченный токен: эмулировать exp в прошлом — ожидается 401.

Безопасность и конфигурация (жёсткие рекомендации)

  • SECRET_KEY должен быть длиной минимум 32 байта и храниться вне репозитория.
  • Настройте HTTPS везде, где передаёте токены.
  • HttpOnly = True защищает от доступа JavaScript к куке.
  • Secure = True передаёт куку только по HTTPS.
  • SameSite = ‘Lax’ или ‘Strict’ снижает риск CSRF.
  • Рассмотрите хранение токена в памяти клиента (например, при SPA) с дополнительной защитой от XSS.

Совместимость и миграция

Если у вас сейчас серверные сессии и вы переходите на JWT:

  • План миграции: поддержка параллельных механизмов (dual auth) на время переноса.
  • Создайте период, когда оба механизма валидны, затем постепенно выключайте старые сессии.

Шаблон ответа API (рекомендация)

  • При успехе возвращайте минимально необходимые данные (message, user_id, token как кука).
  • При ошибке используйте однотипную структуру: {“error”: “описание”, “code”: “AUTH_INVALID”}.

Быстрая проверка через curl

Регистрация:

curl -X POST http://localhost:5000/api/register -H "Content-Type: application/json" -d '{"username":"test","password":"pass"}'

Логин (и сохранение cookie в файле):

curl -i -c cookies.txt -X POST http://localhost:5000/api/login -H "Content-Type: application/json" -d '{"username":"test","password":"pass"}'

Доступ к защищённому маршруту используя сохранённые куки:

curl -b cookies.txt http://localhost:5000/api/users

Пример улучшенного декоратора с проверкой exp и blacklist

# Примерный код для проверки токена с blacklist
from flask import jsonify, request
import jwt
from functools import wraps

blacklist = set()  # в продакшене — коллекция в БД

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.cookies.get('token')
        if not token:
            return jsonify({'message': 'Token is missing!'}), 401
        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            if data.get('jti') in blacklist:
                return jsonify({'message': 'Token revoked!'}), 401
            current_user = User.find_by_id(db.users, data['user_id'])
        except jwt.ExpiredSignatureError:
            return jsonify({'message': 'Token has expired!'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'message': 'Invalid token!'}), 401
        return f(current_user, *args, **kwargs)
    return decorated

Краткое резюме

JSON Web Tokens — удобный и эффективный инструмент для аутентификации в Flask. Однако важно сочетать JWT с надёжными практиками безопасности: хранение секретов вне кода, срок действия токенов, безопасные cookie, возможность отзыва токенов и мониторинг. Следуя приведённым рекомендациям, вы уменьшите риск компрометации и подготовите систему к продакшен‑нагрузкам.

Сводка:

  • JWT удобны для stateless аутентификации, но требуют дополнительных мер безопасности.
  • Используйте exp, jti, безопасные cookie и храните SECRET_KEY в окружении.
  • Рассмотрите refresh tokens и механизм отзыва для контроля сессий.

Важно: перед деплоем проверьте все механизмы на тестовом окружении и проведите security review.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство