Telegram Mini Apps (раніше Web Apps) надають потужний інструмент для створення інтерактивних веб-інтерфейсів усередині Telegram. Однак, як і в будь-якому веб-застосунку, критично важливо забезпечити безпеку даних, що передаються від клієнта до сервера. Одним із ключових елементів цієї безпеки є перевірка initData — рядка, що містить інформацію про користувача, бота та сам Mini App, яка передається під час запуску. У цій статті ми детально розберемо, як валідувати initData на PHP, щоб захистити ваш застосунок від підроблених запитів.
Що таке initData і навіщо її перевіряти?
initData — це рядок, який Telegram WebApp SDK генерує та передає вашому веб-застосунку. Він містить різні параметри, такі як user, chat, query_id, auth_date і, найголовніше, hash. Цей hash використовується для перевірки цілісності та автентичності всієї іншої частини initData. Якщо ви не перевірите initData, зловмисник може легко підробити дані користувача, видати себе за іншого або надіслати некоректні запити, що призведе до вразливостей у вашому застосунку.
Отримання та підготовка даних
Першим кроком є отримання рядка initData із запиту (зазвичай це POST-параметр або заголовок) та його розбір. initData є URL-кодованим рядком запиту, який необхідно перетворити в асоціативний масив.
<?php
/**
* Валидирует initData Telegram Mini Apps.
* @param string $initDataRaw Строка initData, полученная от клиента.
* @param string $botToken Токен вашего Telegram-бот а.
* @param int $maxAuthDateLifetimeSeconds Максимальное время жизни auth_date в секундах.
* @return array|false Ассоциативный массив с разобранными данными или false в случае ошибки валидации.
*/
function validateTelegramInitData(string $initDataRaw, string $botToken, int $maxAuthDateLifetimeSeconds = 3600)
{
// 1. Разбираем query string
parse_str($initDataRaw, $parsedData);
if (!isset($parsedData['hash'])) {
// Хеш отсутствует, данные невалидны
return false;
}
$hash = $parsedData['hash'];
unset($parsedData['hash']); // Удаляем хеш для дальнейшей обработки
// 2. Сортируем параметры по ключу и формируем строку для хеширования
ksort($parsedData);
$dataCheckString = [];
foreach ($parsedData as $key => $value) {
$dataCheckString[] = $key . '=' . $value;
}
$dataCheckString = implode("\n", $dataCheckString);
// 3. Проверяем auth_date
if (!isset($parsedData['auth_date']) || !is_numeric($parsedData['auth_date'])) {
// auth_date отсутствует или некорректен
return false;
}
$authDate = (int)$parsedData['auth_date'];
if (time() - $authDate > $maxAuthDateLifetimeSeconds) {
// Данные устарели
return false;
}
// 4. Возвращаем разобранные данные для дальнейшего использования
return $parsedData;
}
// Пример использования (в реальном приложении токен должен быть из getenv или params)
$initDataFromClient = $_POST['initData'] ?? ''; // Или $_GET['initData'] или из заголовка
$botToken = getenv('TELEGRAM_BOT_TOKEN'); // Получаем токен из переменных окружения
if (empty($initDataFromClient) || empty($botToken)) {
// Обработка отсутствия данных или токена
header('HTTP/1.1 400 Bad Request');
echo json_encode(['error' => 'Missing initData or bot token.']);
exit;
}
$validatedData = validateTelegramInitData($initDataFromClient, $botToken);
if ($validatedData === false) {
header('HTTP/1.1 403 Forbidden');
echo json_encode(['error' => 'Invalid initData.']);
exit;
}
// Данные валидны, можно использовать $validatedData
// Например, для аутентификации пользователя или сохранения данных
echo json_encode(['status' => 'success', 'data' => $validatedData]);
?>
Генерація секретного ключа з токена бота
Згідно з документацією Telegram, секретний ключ для хешування отримується шляхом застосування SHA256 до рядка 'WebAppData' з використанням токена бота як ключа. Цей ключ потім використовується для HMAC-SHA256 хешування data_check_string.
<?php
/**
* Валидирует initData Telegram Mini Apps.
* @param string $initDataRaw Строка initData, полученная от клиента.
* @param string $botToken Токен вашего Telegram-бота.
* @param int $maxAuthDateLifetimeSeconds Максимальное время жизни auth_date в секундах.
* @return array|false Ассоциативный массив с разобранными данными или false в случае ошибки валидации.
*/
function validateTelegramInitData(string $initDataRaw, string $botToken, int $maxAuthDateLifetimeSeconds = 3600)
{
parse_str($initDataRaw, $parsedData);
if (!isset($parsedData['hash'])) {
return false;
}
$hash = $parsedData['hash'];
unset($parsedData['hash']);
ksort($parsedData);
$dataCheckString = [];
foreach ($parsedData as $key => $value) {
$dataCheckString[] = $key . '=' . $value;
}
$dataCheckString = implode("\n", $dataCheckString);
// Проверка auth_date
if (!isset($parsedData['auth_date']) || !is_numeric($parsedData['auth_date'])) {
return false;
}
$authDate = (int)$parsedData['auth_date'];
if (time() - $authDate > $maxAuthDateLifetimeSeconds) {
return false;
}
// 5. Генерируем секретный ключ
$secretKey = hash_hmac('sha256', 'WebAppData', $botToken, true);
// 6. Вычисляем хеш на стороне сервера
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
// 7. Сравниваем полученный хеш с вычисленным (timing-safe)
if (!hash_equals($hash, $calculatedHash)) {
return false;
}
return $parsedData;
}
// Пример использования в Laravel контроллере
// app/Http/Controllers/WebAppController.php
// use Illuminate\Http\Request;
// use Illuminate\Support\Facades\Log;
// class WebAppController extends Controller
// {
// public function processInitData(Request $request)
// {
// $initDataRaw = $request->input('initData');
// $botToken = config('services.telegram_bot_api.token'); // Из config/services.php
// if (empty($initDataRaw) || empty($botToken)) {
// Log::warning('Missing initData or bot token.', ['initData' => $initDataRaw]);
// return response()->json(['error' => 'Missing initData or bot token.'], 400);
// }
// $validatedData = validateTelegramInitData($initDataRaw, $botToken);
// if ($validatedData === false) {
// Log::warning('Invalid initData received.', ['initData' => $initDataRaw]);
// return response()->json(['error' => 'Invalid initData.'], 403);
// }
// // Данные валидны, можно использовать $validatedData
// // Например, для аутентификации пользователя или сохранения данных
// Log::info('InitData successfully validated.', ['user_id' => $validatedData['user']['id'] ?? 'N/A']);
// return response()->json(['status' => 'success', 'data' => $validatedData]);
// }
// }
?>
Timing-safe порівняння хешів
Звичайне порівняння рядків за допомогою оператора == або === може бути вразливим до атак за часом (timing attacks). Зловмисник може вимірювати час виконання порівняння, щоб вгадати символи хешу по одному. Для запобігання цьому використовуйте функцію hash_equals(), яка порівнює рядки за сталий час, незалежно від того, на якому символі вони починають відрізнятися.
Перевірка auth_date
Параметр auth_date містить мітку часу (Unix timestamp) запуску Mini App. Вкрай важливо перевіряти, що ця мітка часу не занадто застаріла. Якщо не встановити обмеження на час життя auth_date, зловмисник може перехопити валідний initData і використати його повторно через тривалий час. Рекомендується встановлювати максимальний час життя auth_date у межах кількох хвилин (наприклад, 1 година або менше), щоб мінімізувати ризик атак повторного відтворення.
Обробка помилок та логування
У разі будь-якої помилки валідації (відсутність хешу, некоректний auth_date, незбіг хешів) ваш застосунок повинен відхилити запит і повернути відповідний HTTP-статус (наприклад, 403 Forbidden або 400 Bad Request). Важливо також логувати ці події, щоб відстежувати потенційні спроби атак або помилки в роботі клієнтської частини.
Використання у фреймворках (Yii2, Laravel)
У фреймворках, таких як Yii2 або Laravel, ви можете інкапсулювати логіку валідації в окремий сервіс, компонент або навіть middleware. Це дозволить легко повторно використовувати код та підтримувати чистоту архітектури.
Приклад для Yii2
<?php
namespace app\components;
use Yii;
use yii\base\Component;
class TelegramWebAppValidator extends Component
{
public $botToken; // Указывается в конфигурации приложения
public $maxAuthDateLifetimeSeconds = 3600; // 1 час по умолчанию
public function validateInitData(string $initDataRaw): array|false
{
if (empty($this->botToken)) {
Yii::error('Telegram bot token is not configured.', __METHOD__);
return false;
}
parse_str($initDataRaw, $parsedData);
if (!isset($parsedData['hash'])) {
Yii::warning('InitData: hash is missing.', __METHOD__);
return false;
}
$hash = $parsedData['hash'];
unset($parsedData['hash']);
ksort($parsedData);
$dataCheckString = [];
foreach ($parsedData as $key => $value) {
$dataCheckString[] = $key . '=' . $value;
}
$dataCheckString = implode("\n", $dataCheckString);
if (!isset($parsedData['auth_date']) || !is_numeric($parsedData['auth_date'])) {
Yii::warning('InitData: auth_date is missing or invalid.', __METHOD__);
return false;
}
$authDate = (int)$parsedData['auth_date'];
if (time() - $authDate > $this->maxAuthDateLifetimeSeconds) {
Yii::warning('InitData: auth_date is too old. Timestamp: ' . $authDate, __METHOD__);
return false;
}
$secretKey = hash_hmac('sha256', 'WebAppData', $this->botToken, true);
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
if (!hash_equals($hash, $calculatedHash)) {
Yii::warning('InitData: hash mismatch. Provided: ' . $hash . ', Calculated: ' . $calculatedHash, __METHOD__);
return false;
}
return $parsedData;
}
}
// В конфигурации web.php:
/*
'components' => [
'telegramWebAppValidator' => [
'class' => 'app\components\TelegramWebAppValidator',
'botToken' => getenv('TELEGRAM_BOT_TOKEN'),
'maxAuthDateLifetimeSeconds' => 1800, // 30 минут
],
// ... другие компоненты
],
*/
// В контроллере:
/*
class MyWebAppController extends \yii\web\Controller
{
public function actionProcessData()
{
$initDataRaw = Yii::$app->request->post('initData');
$validatedData = Yii::$app->telegramWebAppValidator->validateInitData($initDataRaw);
if ($validatedData === false) {
Yii::$app->response->statusCode = 403;
return ['error' => 'Invalid initData.'];
}
// ... обработка валидных данных
return ['status' => 'success', 'data' => $validatedData];
}
}
*/
?>
Висновок
Валідація initData — це обов'язковий крок при розробці безпечних Telegram Mini Apps. Дотримуючись описаних вище кроків, ви зможете надійно перевіряти автентичність даних, запобігати підробкам та забезпечувати цілісність вашого застосунку. Не забувайте завжди отримувати токен бота з безпечного джерела (переменные окружения, конфігураційні файли), а не зберігати його у відкритому коді.
Для створення потужних і безпечних Telegram-ботів та Mini Apps зверніться до професіоналів BotCreator на botservice.biz.