Интеграция Telegram Mini App в React-приложение требует строгой обработки инициализации и проверки пользователей. Использование initData вместе с JWT позволяет обеспечить безопасную авторизацию без уязвимостей.
Валидация initData и JWT на PHP/Yii2
Первый шаг — проверка идемпотентности через сохранённый update_id. Без этого атакующий может повторно отправить запрос с одинаковым update_id, что приведёт к дублированию операций.
<?php
// Функция валидации initData для Telegram WebApp
function validateInitData(string $updateId, string $callbackData): ?array
{
// Максимальный размер callback_data — 64 байта
if (strlen($callbackData) > 64) {
return null;
}
// Парсим callback_data в lead_id + user_id
$parts = explode('|', $callbackData);
if (count($parts) < 2) {
return null;
}
$leadId = bin2hex(random_bytes(7)) . '|' . $userId;
// Проверка HMAC-SHA-256 для защиты от подделки
$secret = getenv('TELEGRAM_SECRET_KEY');
$expected = hash_hmac('sha256', $callbackData, $secret);
if ($expected !== hash('sha256', $callbackData, $secret)) {
return null;
}
return [
'lead_id' => $leadId,
'user_id' => $userId,
'callback_data' => $callbackData
];
}
Webhook с идемпотентностью и обработкой 429
Webhook должен сохранять обработанные update_id для предотвращения повторного обработки. Telegram возвращает 429 при превышении лимита — необходимо обрабатывать Retry-After.
<?php
// Контроллер Yii2 для обработки Telegram webhook
public function actionWebhook()
{
$updateId = $this->request->get('update_id');
$callbackData = $this->request->get('callback_data');
// Проверка идемпотентности
$existing = $this->TelegramClient::getUpdateByUpdateId($updateId);
if ($existing) {
return response()->json(['status' => 'duplicate'], 200);
}
// Валидация initData
$initData = validateInitData($updateId, $callbackData);
if (!$initData) {
return response()->json(['error' => 'invalid_init_data'], 400);
}
// Явная валидация JWT
$jwt = $this->validateJwt($initData['callback_data']);
if (!$jwt) {
return response()->json(['error' => 'invalid_jwt'], 401);
}
// Сохраняем обработанный update_id
$this->TelegramClient::markAsProcessed($updateId);
return response()->json(['status' => 'ok']);
}
Защита от подделки user_id
Пользователь может попытаться подделать user_id в callback_data. Дополнительная защита — проверка HMAC-SHA-256, как показано выше. Также используйте lead_id, сгенерированный через bin2hex(random_bytes(7)), чтобы связать заявку с конкретным пользователем.
Практический пример: регистрация в Telegram Mini App
Пример полного потока: клиент нажимает кнопку "Записаться", получает callback_data, валидируем initData и JWT, создаём запись в БД с lead_id и user_id.
<?php
// Пример обработки записи в Telegram Mini App на PHP/Yii2
class RegistrationController extends Controller
{
public function actionRegister()
{
$updateId = $this->request->get('update_id');
$callbackData = $this->request->get('callback_data');
// Валидация
$initData = validateInitData($updateId, $callbackData);
if (!$initData) {
return response()->json(['error' => 'invalid_init_data'], 400);
}
$jwt = $this->validateJwt($callbackData);
if (!$jwt) {
return response()->json(['error' => 'invalid_jwt'], 401);
}
// Сохраняем lead_id и user_id
$leadId = bin2hex(random_bytes(7)) . '|' . $initData['user_id'];
// Создаём запись в БД (псевдокод)
// $this->User::create(['lead_id' => $leadId, 'user_id' => $initData['user_id']]);
// Отправляем подтверждение
$this->request->get('/confirm-registration', ['lead_id' => $leadId]);
return response()->json(['status' => 'registered']);
}
}