React Telegram Mini App: Авторизація через initData та JWT на PHP/Yii2

Интеграция 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']);
}
}

Нові статті — у Telegram

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.