Telegram Mini Apps (formerly Web Apps) provide a powerful tool for creating interactive web interfaces inside Telegram. However, as in any web application, it is critically important to ensure the security of data transmitted from the client to the server. One of the key elements of this security is verifying initData — a string containing information about the user, the bot, and the Mini App itself, which is passed upon launch. In this article, we will cover in detail how to validate initData in PHP to protect your application from forged requests.
What is initData and why should you verify it?
initData is a string that the Telegram WebApp SDK generates and passes to your web application. It contains various parameters such as user, chat, query_id, auth_date, and most importantly, hash. This hash is used to verify the integrity and authenticity of the rest of the initData. If you do not verify initData, an attacker can easily forge user data, impersonate someone else, or send invalid requests, leading to vulnerabilities in your application.
Extracting and preparing data
The first step is retrieving the initData string from the request (usually a POST parameter or header) and parsing it. initData is a URL-encoded query string that needs to be converted into an associative array.
<?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]);
?>
Generating a secret key from the bot token
According to the Telegram documentation, the secret key for hashing is obtained by applying SHA256 to the string 'WebAppData' using the bot token as the key. This key is then used for HMAC-SHA256 hashing of 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 hash comparison
A standard string comparison using the == or === operator can be vulnerable to timing attacks. An attacker can measure the execution time of the comparison to guess the hash characters one by one. To prevent this, use the hash_equals() function, which compares strings in constant time, regardless of where the characters start to differ.
Checking auth_date
The auth_date parameter contains a Unix timestamp of when the Mini App was launched. It is crucial to verify that this timestamp is not too old. If you do not set a limit on the auth_date lifespan, an attacker could intercept a valid initData and reuse it long after. It is recommended to set a maximum lifetime for auth_date within a few minutes (for example, 1 hour or less) to minimize the risk of replay attacks.
Error handling and logging
In case of any validation error (missing hash, invalid auth_date, hash mismatch), your application should reject the request and return an appropriate HTTP status code (e.g., 403 Forbidden or 400 Bad Request). It is also important to log these events to track potential attack attempts or errors in client-side operation.
Usage in frameworks (Yii2, Laravel)
In frameworks such as Yii2 or Laravel, you can encapsulate the validation logic in a separate service, component, or even middleware. This allows for easy code reuse and keeps the architecture clean.
Yii2 example
<?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];
}
}
*/
?>
Conclusion
Validation of initData is a mandatory step when developing secure Telegram Mini Apps. By following the steps described above, you will be able to reliably verify data authenticity, prevent forgery, and ensure the integrity of your application. Remember to always obtain the bot token from a secure source (переменные окружения, configuration files) rather than storing it in open code.
To create powerful and secure Telegram bots and Mini Apps, contact the professionals at BotCreator via botservice.biz.