Telegram Web App bot: разбор и аспекты безопасности June 14, 2022 on Savely Krasovsky's blog

С релизом нового обновления Telegram у многих возникли вопросы как правильно наладить общение бот <-> Telegram, бот <-> бэкенд и сделать это безопасно. Для начала давайте проясним какие способы активации Telegram решил предоставить пользователю:

  1. Через кнопку меню, которую можно настроить у @BotFather.
  2. Через inline-кнопку.
  3. Через keyboard-кнопку.
  4. Через меню вложений.

Виды кнопок

Кнопка меню, inline-кнопка

Первый и второй способ предлагают нам аутентифицировать и авторизовать пользователя через специальный объект initData, который можно достать с помощью JavaScript. Объект имеет следующую структуру:

{
  "query_id": "str",
  "user": {
    "id": 1,
    "is_bot": false,
    "first_name": "Pavel",
    "last_name": "Durov",
    "username": "durov",
    "language_code": "ru",
    "photo_url": "https://telegram.org/durov.jpg"
  },
  "auth_date": 1655210062,
  "hash": "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"
}

Проблема в том, что официальная документация хоть и предупреждает, но не особо объясняет зачем валидировать поле initData на сервере бота.

TL;DR

Без валидации:

Мэллори: Привет Боб, я Алиса, дай мне информацию о балансе и сделай перевод на имя Мэллори.

Боб: Пожалуйста, Алиса, всё готово.

С валидацией:

Мэллори: Привет Боб, я Алиса, дай мне информацию о балансе и сделай перевод на имя Мэллори.

Боб: Предоставьте, пожалуйста, валидную подпись Алисы.

Мэллори разводит руками

То есть нужно это затем, чтобы произвести безопасную аутентификацию (проверку подлинности запроса) и авторизацию (понять, что к боту пришла именно Алиса, а не Мэллори).

Без этой валидации бот сможет предоставлять критически важную информацию (например, вы делаете онлайн-банкинг в боте) просто по ID пользователя Telegram.

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

Валидация, к слову, не совсем тривиальная. Разработчики Telegram, как обычно, не поленились и вместо проверенного стандарта JSON Web Token (JWT), реализовали свой собственный велосипед, да ещё и на базе обычного HMAC-SHA256 (то есть HS256 будь у нас JWT-токен). В результате initData представляет собой URL-encoded строку query-параметров. Для корректной валидации которой требуется следующая цепочка шагов:

  1. Декодируем строку, используя URL-encoding (важно, иначе значение с ключом user останется не декодированным).
  2. Полученные пары ключ-значения сортируем в алфавитном порядке.
  3. Исключаем ключ hash
  4. Из полученных пар составляем тело вида: auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user>. Важно сохранить значение с ключом user в чистом JSON.
  5. Берем хэш от токена вашего бота с помощью алгоритма HMAC-SHA256 с ключом WebAppData.
  6. Берем хэш от полученного в шаге 4 тела с помощью того же алгоритма, а в качестве ключа используем хэш, полученный ранее в виде последовательности байтов (а не hex-репрезентации!).
  7. Преобразуем полученный хэш в hex-строку и сравниваем со значением ключа hash.

По аналогии с JWT, если валидация прошла успешно, пользователя можно считать аутентифицированным и переходить к авторизации с помощью предоставленного payload (в нашем случае это id в объекте user).

Говоря о query_id, который мы получаем в initData. Используя это поле, мы можем создать в чате сообщение с ботом от имени пользователя с бейджем via @your_bot. Для этого потребуется вызвать метод answerWebAppQuery.

Вид такого сообщения

+ может использоваться для приложений любой сложности
+ initData по факту является полноценным stateless-токеном по типу JWT
+ позволяет создавать сообщения от имени пользователя в чате с ботом с бейджем via @your_bot
- требуется собственный бэкенд для веб-части для валидации initData и работы с пользователем

Keyboard-кнопка

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

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

Однако при использовании этого способа появляется возможность использовать метод Telegram.WebApp.sendData(), который позволяет отправить сообщение боту напрямую, а тот предоставит его вам через long-polling или вебхуки. Стоит учесть, что после успешного выполнения веб-окно автоматически закроется, а бот отрапортует сервисным сообщением Вы успешно передали данные боту кнопкой «Test button».

Поэтому Telegram позиционирует этот способ как удобный способ сделать гибкую веб-форму ввода с полями типа date picker. Вернуть значения формы можно с помощью метода Telegram.WebApp.sendData().

Нужно понимать, что в JS-файле этот метод является лишь прослойкой, само значение, переданное в sendData() отправляются далее через MTProto-метод sendWebAppData. Методы MTProto невозможно использовать без авторизации в Telegram, поэтому тут мессенджер берет безопасность полностью на себя.

В этом заключается плюс этого метода.

+ удобно для заполнения сравнительно простых форм ввода
+ наличие собственного бэкенда для Web-части не требуется
- initData не приходит, возможность авторизовать пользователя на своём бэкенде (даже если он есть) отсутствует
- отправить информацию боту можно только 1 раз

Кнопка в меню вложений

Есть также четвертый способ, который технически не отличается от первого и второго (только дополнительными полями в initData), но в этом случае бот добавляется в меню вложений.

К сожалению эта возможность пока что доступна только для тех, кто участвует в рекламной платформе Telegram и, следовательно, внёс залог 2 миллиона евро. Поэтому пока что говорить тут особо не о чем. Из известных мне публичных ботов такую интеграцию использует @wallet.

Основной плюс — возможность использовать бота не только в личной переписке с ботом. Например, вышеупомянутый @wallet позволяет отправить любому собеседнику сообщение с информацией о переводе, собеседнику остается лишь нажать и получить свой перевод.

Вид такого сообщения

+ плюсы первых двух способов
+ возможность использовать бота в переписке с человеком
+ новый интуитивный механизм использования ботов Telegram
- минусы первых двух способов
- необходимо стать крупным рекламодателем на площадке Telegram