С релизом нового обновления Telegram у многих возникли вопросы как правильно наладить общение бот <-> Telegram
,
бот <-> бэкенд
и сделать это безопасно. Для начала давайте проясним какие способы активации Telegram решил
предоставить пользователю:
- Через кнопку меню, которую можно настроить у
@BotFather
. - Через inline-кнопку.
- Через keyboard-кнопку.
- Через меню вложений.
Кнопка меню, 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-параметров. Для корректной валидации которой требуется следующая цепочка шагов:
- Декодируем строку, используя URL-encoding (важно, иначе значение с ключом
user
останется не декодированным). - Полученные пары ключ-значения сортируем в алфавитном порядке.
- Исключаем ключ
hash
- Из полученных пар составляем тело вида:
auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user>
. Важно сохранить значение с ключомuser
в чистом JSON. - Берем хэш от токена вашего бота с помощью алгоритма HMAC-SHA256 с ключом
WebAppData
. - Берем хэш от полученного в шаге 4 тела с помощью того же алгоритма, а в качестве ключа используем хэш, полученный ранее в виде последовательности байтов (а не hex-репрезентации!).
- Преобразуем полученный хэш в 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