Как сделать вход на сайт с помощью Telegram-бота?

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

Клиент можно подделать. Доверяйте только серверной проверке:

  1. Соберите все поля кроме hash в пары key=value.
  2. Отсортируйте по ключу и склейте через \n.
  3. Секрет: SHA256(bot_token) как бинарный ключ.
  4. Считайте hash_hmac('sha256', …) и сравните с hash через hash_equals.
  5. Отклоните данные, если 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 бота.

  1. Сайт создаёт короткий токен, сохраняет его со статусом pending и TTL (например 15 минут).
  2. Пользователь открывает https://t.me/YourBotUsername?start=login_TOKEN.
  3. Бот в обработчике /start читает payload, подтверждает токен и пишет telegram_user_id / chat_id.
  4. Браузер опрашивает или переходит на /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 или оставьте задачу на оценку.

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

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