React Telegram Mini App: Authentication via initData and JWT in PHP/Yii2

Интеграция Telegram Mini App into a React application requires strict initialization handling and user verification. Using initData together with JWT ensures secure authorization without vulnerabilities.

initData and JWT Validation in PHP/Yii2

The first step is проверка идемпотентности via the saved update_id. Without this, an attacker can resend a request with the same update_id, leading to duplicate operations.

<?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 with Idempotency and 429 Handling

The webhook must save processed update_ids to prevent re-processing. Telegram returns 429 when limits are exceeded — you must handle 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']);
}

Protection Against user_id Spoofing

A user may attempt to spoof the user_id in callback_data. Additional protection is HMAC-SHA-256 verification, as shown above. Also, use a lead_id generated via bin2hex(random_bytes(7)) to link the request to a specific user.

Practical Example: Registration in Telegram Mini App

Full flow example: the client clicks the "Sign up" button, receives callback_data, we validate initData and JWT, and create a record in the database with lead_id and 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']);
}
}

New articles on Telegram

We explain what to automate in your business and how it works in practice. No spam.