Flask-WTF, WTForms и защита от CSRF

Flask-WTF превращает форму в класс Python: поля, валидаторы и CSRF-защита описываются декларативно, а view-функция становится короткой и чистой.
Ручная валидация быстро разрастается. Flask-WTF собирает правила формы в одном классе: объявил поля и валидаторы — и метод validate_on_submit сам проверит данные и защитит от CSRF. View сжимается до «создать форму, проверить, сохранить, редиректнуть».

Flask-WTF — обёртка над библиотекой WTForms. Форма описывается классом, наследующим FlaskForm; поля — это атрибуты класса с типом и списком валидаторов. Это декларативный стиль: ты описываешь что должно быть верным, а не как проверять.

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Email, Length

class RegisterForm(FlaskForm):
    name = StringField("Имя", validators=[DataRequired()])
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Пароль", validators=[Length(min=8)])

Во view форма создаётся и проверяется методом validate_on_submit — он истина только если запрос POST, форма валидна и CSRF-токен верный:

@app.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        # form.name.data, form.email.data ...
        return redirect(url_for("success"))
    return render_template("register.html", form=form)

CSRF (подделка межсайтовых запросов) — атака, где чужой сайт втайне отправляет запрос от имени залогиненного пользователя. Flask-WTF вставляет в форму скрытый токен и проверяет его при сабмите. Для этого обязателен SECRET_KEY — им подписывается токен.

Декларативный стиль Flask-WTF — это про то, чтобы правила формы жили в одном месте и были видны с первого взгляда. Вместо россыпи ручных if по полям ты объявляешь класс: вот поля, вот их валидаторы. View после этого сжимается до канонической четвёрки строк — создать форму, validate_on_submit, выполнить бизнес-логику, редирект. Бонусом идёт автоматическая защита от CSRF, которую вручную реализовать правильно непросто. Стоит помнить и о границах применимости: Flask-WTF заточен под классические HTML-формы; если строишь чистый JSON-API, форму с CSRF-токеном обычно заменяют на валидацию тела через Pydantic или Marshmallow и другую защиту от подделки запросов.

Как работает под капотом

CSRF-защита работает так: при рендере формы сервер кладёт в неё уникальный подписанный токен и запоминает его в сессии. При сабмите токен из формы сверяется с сохранённым. Чужой сайт токена не знает — его запрос отклоняется.

  GET /register
     │ сервер генерирует токен T, кладёт в форму и в сессию
     ▼
  <input type=hidden name=csrf_token value=T>
     │ пользователь сабмитит
     ▼
  POST: токен из формы == токен в сессии?
     ├─ да  → форма принята
     └─ нет → 400, запрос отклонён (защита от CSRF)

Смоделируем проверку токена обычным Python.

import secrets

session = {}

def render_form():
    token = secrets.token_hex(8)
    session["csrf"] = token
    return token            # уходит в скрытое поле формы

def submit(form_token):
    expected = session.get("csrf")
    if form_token != expected:
        return "400 CSRF mismatch"
    return "200 принято"

t = render_form()
print(submit(t))            # верный токен
print(submit("подделка"))   # чужой сайт не знает токен

Запусти: запрос с верным токеном принимается, с поддельным — отклоняется. Так Flask-WTF отсекает подделанные межсайтовые запросы автоматически для каждой формы.

Частые ошибки

  • Не задать SECRET_KEY. Без него CSRF-защита не работает, а Flask-WTF ругается.
  • Забыть {{ form.csrf_token }} в шаблоне. Без скрытого токена сабмит провалится с 400.
  • Проверять request.method вместо validate_on_submit. Метод уже включает проверку метода, валидности и CSRF.

Best practices

  • Держи SECRET_KEY в переменных окружения, не в коде.
  • Защищай CSRF все формы, меняющие состояние.
  • Правила валидации — в классе формы, view оставляй тонким.

Что запомнить

  • Flask-WTF описывает форму классом с полями и валидаторами.
  • validate_on_submit проверяет: POST + валидность + CSRF-токен.
  • Скрытый подписанный csrf_token защищает от подделки межсайтовых запросов.
  • Для работы CSRF обязателен SECRET_KEY; держи его вне кода.

Итог: Flask-WTF описывает форму классом с валидаторами, validate_on_submit делает всю проверку, а скрытый подписанный токен защищает от CSRF (нужен SECRET_KEY). Дальше — сессии, куки и flash-сообщения.

Проверьте себя
1. Что проверяет form.validate_on_submit()?
AТолько что запрос POST
BЧто запрос POST, форма валидна и CSRF-токен верный
CТолько наличие SECRET_KEY
DПодключение к базе данных
2. От какой атаки защищает скрытый csrf_token в форме?
ASQL-инъекции
BXSS
CCSRF — подделки запроса с чужого сайта от имени пользователя
DDDoS