Подтвердите виджет входа в Telegram на PHP и Yii2: хэш HMAC-SHA-256, срок действия auth_date и привязка telegram_id к пользов…

Подтвердите виджет входа в Telegram на PHP и Yii2

Виджет входа в Telegram - это небольшая кнопка iframe, которая возвращает поля профиля пользователя плюс хэшей подписан токеном бота. Виджет удобен, но каждое поле в полезной нагрузке можно контролировать злоумышленником, в том числе id, Дата авторизации, и отображаемые first_name. Подпись - это единственное, что доказывает, что полезная нагрузка пришла из Telegram.

В этом руководстве мы реализуем путь проверки, который вы должны отправить:

1. Получить полезную нагрузку виджета (id, first_name, фамилия, имя пользователя, Фото URL, Дата авторизации, хэшей). 2. Пересчитать HMAC-SHA-256(bot_token, data_check_string) и сравните его с хэшей. 3. Обеспечить Дата авторизации окно, чтобы украденная полезная нагрузка не могла воспроизводиться вечно. 4. Привязать Телеграм ID внутреннему пользователю в Yii2, создавая пользователя с первого взгляда.

Что делает эта статья not утверждение: он не заменяет аутентификацию на стороне сервера сам по себе — виджет является утверждением удостоверения, и вы по-прежнему выдаете свой собственный сеанс. Мы рассматриваем его как подтвержденный логин, а не как авторизацию.

1. Что на самом деле отправляет виджет

Когда пользователь нажимает кнопку и Telegram аутентифицирует их, iframe перенаправляет обратно на ваш data-onauth обратный звонок (или вы захватываете поля из глобального TelegramLoginWidget callback) - Telegram добавляет поля в качестве параметров URL-запроса. Типичная полезная нагрузка выглядит следующим образом:

id=12345678
first_name=Alex
last_name=Ivanov
username=alex_ivanov
photo_url=https%3A%2F%2Ft.me%2Fi%2Fuserpic%2F...%2F...jpg
auth_date=1716300000
hash=9f2c5b1e8a4f4d0c...e6

Обратите внимание, что Telegram отправляет id в виде строки, но это всегда числовой идентификатор пользователя. Дата авторизации - секунды Unix. хэшей представляет собой шестнадцатеричное кодирование SHA-256.

2. Алгоритм верификации, прямо из документов

Подписанная строка строится из полей *other*, объединенных как значение ключа строки, отсортированные по ключу, разделенные \n. Строки с пустыми значениями отбрасываются. Затем:

sha256_hex = HMAC-SHA-256(bot_token, data_check_string)

Где ключ HMAC - это сам маркер бота (в виде строки UTF-8), а выход - это шестнадцатеричный дайджест (64 строчных шестнадцатеричных символа). Результат должен равняться хэшей поля в постоянном времени.

Правительство не располагает API бота звоните сюда. Все работает локально — нет сетевого туда и обратно, нет ограничения скорости, нет токена, отправленного третьей стороне.

3. Верификатор Plain-PHP, который вы можете зайти

Этот блок не содержит фреймворка. Он возвращает структурированный результат, поэтому вы можете разветвить его.

<?php
declare(strict_types=1);

final class TelegramLoginVerifier
{
private const CLOCK_SKEW_SECONDS = 300; // 5 minutes

public function __construct(
private readonly string $botToken,
private readonly int $now,
) {}

/**
* @param array<string,string> $payload
* @return array{ok:bool, reason?:string, profile?:array{id:int,first_name:string,last_name:?string,username:?string,photo_url:?string}}
*/
public function verify(array $payload): array
{
$hash = $payload['hash'] ?? '';
unset($payload['hash']);

if ($hash === '' || !preg_match('/^[a-f0-9]{64}$/', $hash)) {
return ['ok' => false, 'reason' => 'bad_hash_format'];
}

$authDate = (int)($payload['auth_date'] ?? 0);
if ($authDate <= 0 || abs($this->now - $authDate) > self::CLOCK_SKEW_SECONDS) {
return ['ok' => false, 'reason' => 'auth_date_expired'];
}

$pairs = [];
foreach ($payload as $k => $v) {
if ($v === '' || $v === null) {
continue; // Telegram drops empty values before signing
}
$pairs[] = $k . '=' . $v;
}
sort($pairs, SORT_STRING);
$dataCheckString = implode("\n", $pairs);

$secretKey = hash('sha256', $this->botToken, true); // raw 32-byte key
$computed = hash_hmac('sha256', $dataCheckString, $secretKey);

if (!hash_equals($computed, $hash)) {
return ['ok' => false, 'reason' => 'bad_signature'];
}

return [
'ok' => true,
'profile' => [
'id' => (int)$payload['id'],
'first_name'=> (string)$payload['first_name'],
'last_name' => $payload['last_name'] ?? null,
'username' => $payload['username'] ?? null,
'photo_url' => $payload['photo_url'] ?? null,
],
];
}
}

Следует отметить две вещи:

- HMAC клавиша является hash('sha256', $botToken, true) — *двоичный* SHA-256 маркера. Забыв true флаг является наиболее распространенной ошибкой в сторонних учебниках; он выдает неправильный ключ. - Мы используем hash_equals Для сравнения, === на шестнадцатеричных строках не хватает времени, хотя практический риск веб-запроса невелик.

4. Подключение к контроллеру Yii2

Положим Токен бота подвергается воздействию через параметры Yii, никогда не кодируется жестко. Типичная конфигурация:

// config/params.php
return [
'telegramBotToken' => getenv('TELEGRAM_BOT_TOKEN') ?: '',
];

Контроллер принимает полезную нагрузку GET, запускает проверку и либо привязывается к существующему пользователю, либо создает его. Используйте миграцию, которая добавляет Телеграм ID (BIGINT UNIQUE NULL) и last_telegram_login_at запись.

<?php
declare(strict_types=1);

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\User;
use app\security\TelegramLoginVerifier;

final class TelegramAuthController extends Controller
{
public function actionCallback(): \yii\web\Response
{
$token = (string)Yii::$app->params['telegramBotToken'];
if ($token === '') {
throw new \yii\web\HttpException(500, 'Bot token not configured');
}

$payload = Yii::$app->request->get();
$verifier = new TelegramLoginVerifier($token, time());
$result = $verifier->verify($payload);

if (!$result['ok']) {
Yii::warning('Telegram login rejected: ' . $result['reason'], __METHOD__);
return $this->redirect(['site/login', 'error' => 'telegram_verification_failed']);
}

$profile = $result['profile'];

$user = User::findOne(['telegram_id' => $profile['id']]);
if ($user === null) {
$user = new User([
'telegram_id' => $profile['id'],
'username' => $profile['username'] ?? ('tg_' . $profile['id']),
'first_name' => $profile['first_name'],
'last_name' => $profile['last_name'],
'last_telegram_login_at'=> time(),
]);
if (!$user->save(false)) { // validate() rules should accept the payload
throw new \yii\web\HttpException(500, 'Cannot create user');
}
} else {
$user->last_telegram_login_at = time();
$user->save(false, ['last_telegram_login_at']);
}

Yii::$app->user->login($user, 3600 * 24 * 30);
return $this->redirect(['site/index']);
}
}

Два свойства безопасности, которые стоит выделить:

- Идемпотентность Проверка не имеет собственных побочных эффектов; только записи происходят внутри транзакции, которая загружает или создает пользователя. - Единый источник получения правдивой информации После User.telegram_id установлен, вы не позволяете виджету перезаписать его позже. Если пользователь Telegram меняет first_name вы обновляете его, но никогда не позволяете виджету выбирать, какая строка в вашем пользователи стол, на котором он приземляется.

5. Необязательно: производственные примечания

- Окно «Часы» 5 минут - это нормальное значение по умолчанию. Меньшие окна ломают законные логины, которые пользователю потребовалось некоторое время для подтверждения; большие окна увеличивают поверхность воспроизведения. - Ротация токенов. Если вы вращаете маркер бота, каждая полезная нагрузка виджета, подписанная старым маркером, терпит неудачу. Нет изящного пути; вращайтесь только тогда, когда вы можете терпеть принудительный повторный вход в систему. - Домен виджета Bot vs Login. Виджет настроен для каждого бота на my.telegram.org, а не через BotFather. Вы можете использовать одного и того же бота как для виджета входа *, так и для * обычных вызовов API бота; они разделяют токен, но виджет не требует нажатия пользователем ПУСК Первый класс - - Выйти Клиринг Yii::$app->user->logout() достаточно на вашей стороне; Telegram не знает о вашей сессии. - Нет вызова API бота. Не звони getChat, getProfilePhotos, или любым другим способом просто «перепроверить» пользователя. Хеш уже является сигнатурой на стороне сервера; дополнительная HTTP-поездка туда и обратно приводит к утечке токена бота в журналы и добавляет задержку.

Если вам нужен виджет *и* беседа с мини-приложением/ ботом для одного и того же пользователя, сохраните Телеграм ID в вашей внутренней записи с первого дня — это ключ присоединения.

---

Telegram-боты и мини-приложения гораздо приятнее отправлять, когда скучные части — верификация входа, сессии и привязки — уже подключены. Команда, стоящая за BotCreator, создает производственные Telegram-боты и мини-приложения, и стоит посмотреть, если вы хотите, чтобы студия сняла эту работу с вашей тарелки.

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.