Telegram Login Widget даёт кнопку «Войти через Telegram» на сайте без OAuth-провайдера и без пароля. Пользователь подтверждает вход в клиенте Telegram, а на ваш HTTPS-endpoint приходит payload с id, auth_date и подписью hash. Ниже — как встроить виджет, проверить HMAC на сервере, привязать telegram_id к аккаунту и, при необходимости, заменить виджет коротким deep-link в бота.
Что нужно заранее
- Бот в @BotFather и его токен (секрет только на сервере).
- Домен сайта, привязанный к боту командой
/setdomainв BotFather. - Публичный HTTPS-URL для callback: localhost и HTTP Telegram не примет.
Без домена в BotFather кнопка не появится или вернёт ошибку. Токен бота никогда не отдавайте в браузер — он нужен только для проверки hash.
1. Встраиваем Login Widget
На странице входа добавьте скрипт Telegram. Атрибут data-telegram-login — username бота без @. Атрибут data-auth-url — абсолютный HTTPS-адрес вашего обработчика (редирект после авторизации).
<!-- страница /login -->
<script async
src="https://telegram.org/js/telegram-widget.js?22"
data-telegram-login="YourBotUsername"
data-size="large"
data-auth-url="https://example.com/auth/telegram"
data-request-access="write">
</script>
После подтверждения Telegram редиректит браузер на ваш URL (GET) и передаёт query-параметры: id, first_name, опционально username, photo_url, auth_date, hash.
Важно:data-auth-urlдолжен быть публичным HTTPS. Туннели вроде ngrok допустимы для отладки, обычныйhttp://localhost— нет.
Подробный разбор проверки подписи в PHP/Yii2 — в статье проверка Telegram Login Widget в PHP и Yii2.
2. Серверная проверка hash и auth_date
Клиент можно подделать. Доверяйте только серверной проверке:
- Соберите все поля кроме
hashв парыkey=value. - Отсортируйте по ключу и склейте через
\n. - Секрет:
SHA256(bot_token)как бинарный ключ. - Считайте
hash_hmac('sha256', …)и сравните сhashчерезhash_equals. - Отклоните данные, если
auth_dateстарше разумного окна (часто 24 часа, лучше короче).
<?php
function verifyTelegramLogin(array $data, string $botToken, int $maxAge = 86400): bool
{
$checkHash = (string)($data['hash'] ?? '');
if ($checkHash === '') {
return false;
}
$pairs = [];
foreach ($data as $key => $value) {
if ($key === 'hash' || !is_scalar($value)) {
continue;
}
$pairs[] = $key . '=' . $value;
}
sort($pairs);
$dataCheckString = implode("\n", $pairs);
$secretKey = hash('sha256', $botToken, true);
$calc = hash_hmac('sha256', $dataCheckString, $secretKey);
if (!hash_equals($calc, $checkHash)) {
return false;
}
$authDate = (int)($data['auth_date'] ?? 0);
if ($authDate < time() - $maxAge) {
return false;
}
return isset($data['id']) && (int)$data['id'] > 0;
}
// Пример обработчика
$data = $_GET; // или $_POST — как настроите callback
if (!verifyTelegramLogin($data, getenv('TELEGRAM_BOT_TOKEN'))) {
http_response_code(403);
exit('Invalid Telegram login');
}
$telegramId = (int)$data['id'];
// дальше: найти или создать пользователя и открыть сессию
Никогда не логируйте bot token и полный payload с hash в публичные логи. Ошибку подписи обрабатывайте одинаково («вход не удался»), чтобы не подсказывать атакующему.
3. Привязка telegram_id к пользователю сайта
После успешной проверки:
- Ищите пользователя по полю
telegram_user_id(или таблице oauth-связок). - Если найден — логиньте его.
- Если гость уже вошёл на сайте — привяжите Telegram к текущему аккаунту (с проверкой, что id не занят другим).
- Иначе создайте аккаунт / oauth-запись и сохраните
idкак стабильный идентификатор.
Login Widget отдаёт user id, но не гарантирует, что пользователь уже нажал Start у бота. Если нужны сообщения в личку, отдельно дождитесь диалога с ботом или используйте deep-link ниже.
Практическая схема проверки и привязки в Yii2 разобрана здесь: verify Telegram Login Widget (PHP/Yii2).
4. Альтернатива: deep-link start=token
Если домен для виджета недоступен (поддомен, staging без /setdomain) или нужен сразу chat_id, используйте одноразовый токен в deep-link бота.
- Сайт создаёт короткий токен, сохраняет его со статусом
pendingи TTL (например 15 минут). - Пользователь открывает
https://t.me/YourBotUsername?start=login_TOKEN. - Бот в обработчике
/startчитает payload, подтверждает токен и пишетtelegram_user_id/chat_id. - Браузер опрашивает или переходит на
/auth/telegram/complete?token=…и получает сессию.
<?php
// сайт: создать сессию входа
$token = bin2hex(random_bytes(10)); // ~20 hex-символов
// сохранить: token, status=pending, expires_at=now+900
$deepLink = 'https://t.me/YourBotUsername?start=login_' . $token;
// бот: обработать message.text вида "/start login_TOKEN"
if (preg_match('#^/start\s+login_([a-f0-9]+)$#', $text, $m)) {
$token = $m[1];
// найти pending-токен, записать from.id и chat.id, status=confirmed
// отправить пользователю кнопку/ссылку на https://example.com/auth/telegram/complete?token=...
}
// сайт: complete
// если token confirmed и не истёк — login / bind telegram_id, status=consumed
Токен должен быть одноразовым: после входа помечайте его consumed. Не кладите в start секрет сессии сайта — только непрозрачный идентификатор записи.
Что выбрать
- Login Widget — привычная кнопка на сайте, минимум UX в боте, нужен
/setdomainи публичный HTTPS callback. - Deep-link — работает без домена виджета, сразу открывает диалог с ботом, чуть больше логики токенов.
Оба пути сходятся в одном: на сервере вы доверяете только проверенному telegram_id и сами открываете сессию пользователя.
Нужна готовая связка сайта и Telegram-бота под ваш домен — посмотрите решения на botservice.biz или оставьте задачу на оценку.