Integrating the official Telegram Login Widget allows users to authenticate on a web application using their Telegram account. When a user authorizes via the widget, Telegram passes authentication parameters (such as id, first_name, username, auth_date, and hash) back to your specified redirect URL or JavaScript callback.
In this tutorial, we will construct a backend validation mechanism in PHP and implement it inside a Yii2 application controller. We will compute the expected HMAC-SHA-256 signature, execute a timing-safe hash comparison, check signature freshness, and bind the incoming telegram_id to your user database.
What this guide covers:
- Constructing the
data_check_stringaccording to Telegram Login Widget specifications. - Generating the secret key via
sha256(bot_token). - Validating payload signatures with
hash_equals(). - Binding verified
telegram_idrecords in Yii2.
What this guide does not cover:
- Setting up frontend JavaScript widgets or HTML embed code.
- Telegram Mini App
initDataverification (which uses a different secret key derivation process).
The Telegram Verification Algorithm
Unlike Mini App initData (which uses the literal string "WebAppData" as the HMAC key), the standard Telegram Login Widget derives its secret key by taking the binary SHA-256 hash of your bot token.
The algorithm steps are:
- Extract the
hashparameter from the incoming payload. - Filter out
hashfrom the remaining keys. - Sort the remaining key-value pairs alphabetically by key name.
- Format each pair as
key=valueand join them with newline characters (\n). - Generate the secret key:
hash('sha256', $botToken, true). - Compute
hash_hmac('sha256', $dataCheckString, $secretKey). - Verify the output against the received hash using
hash_equals(). - Verify that
auth_dateis within an acceptable threshold (e.g., 86,400 seconds).
Here is a reusable PHP service class implementing this verification logic:
<?php
declare(strict_types=1);
namespace app\components;
final class TelegramLoginValidator
{
public function isValid(array $authData, string $botToken, int $maxAgeSeconds = 86400): bool
{
if (!isset($authData['hash'], $authData['auth_date'])) {
return false;
}
$checkHash = (string)$authData['hash'];
unset($authData['hash']);
// Check payload freshness
$authDate = (int)$authData['auth_date'];
if ((time() - $authDate) > $maxAgeSeconds) {
return false;
}
// Filter non-string values and build key=value list
$dataCheckArr = [];
foreach ($authData as $key => $value) {
if (is_scalar($value)) {
$dataCheckArr[] = $key . '=' . $value;
}
}
// Sort keys alphabetically
sort($dataCheckArr, SORT_STRING);
$dataCheckString = implode("\n", $dataCheckArr);
// Secret key is the raw binary SHA-256 hash of the bot token
$secretKey = hash('sha256', $botToken, true);
$hash = hash_hmac('sha256', $dataCheckString, $secretKey);
return hash_equals($hash, $checkHash);
}
}
Integrating with a Yii2 Controller
Next, wire this validator into a Yii2 controller action. We fetch the bot token from environment variables or Yii::$app->params, run the validation check, and either bind the incoming telegram_id to an existing authenticated user session or create a new user account.
Ensure your User database table has an indexed telegram_id column (BIGINT, nullable, unique).
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\web\BadRequestHttpException;
use app\models\User;
use app\components\TelegramLoginValidator;
class AuthController extends Controller
{
public function actionTelegramCallback()
{
$requestParams = Yii::$app->request->get();
$botToken = (string)(getenv('TELEGRAM_BOT_TOKEN') ?: (Yii::$app->params['telegramBotToken'] ?? ''));
if (empty($botToken)) {
throw new \LogicException('Telegram bot token is not configured.');
}
$validator = new TelegramLoginValidator();
if (!$validator->isValid($requestParams, $botToken)) {
throw new BadRequestHttpException('Invalid or expired Telegram authentication signature.');
}
$telegramId = (int)$requestParams['id'];
$username = $requestParams['username'] ?? null;
// Check if a user with this telegram_id already exists
$user = User::findOne(['telegram_id' => $telegramId]);
if (!$user) {
// If currently logged in, link telegram_id to current profile
if (!Yii::$app->user->isGuest) {
/** @var User $user */
$user = Yii::$app->user->identity;
$user->telegram_id = $telegramId;
$user->save(false, ['telegram_id']);
Yii::$app->session->setFlash('success', 'Telegram account connected successfully.');
return $this->redirect(['/user/profile']);
}
// Register a new user account bound to this Telegram identity
$user = new User();
$user->telegram_id = $telegramId;
$user->username = $username ?: 'tg_' . $telegramId;
$user->generateAuthKey(); // Sets random auth_key for session security
if (!$user->save()) {
throw new \RuntimeException('Failed to register user account.');
}
}
// Log the user into the Yii2 application session
Yii::$app->user->login($user, 3600 * 24 * 30);
return $this->redirect(['/site/dashboard']);
}
}
Production Considerations
-
Environment Variable Configuration: Never hardcode bot tokens into code. Store
TELEGRAM_BOT_TOKENin system environment variables or safely loaded.envfiles usinggetenv(). -
Replay Protection: The
auth_datecheck enforces a max window (e.g., 24 hours). If your application handles high-value transactions, consider storing consumedhashstrings in Redis for the duration of the expiration window to prevent replay attacks entirely. - Account Linking Logic: If a user is already authenticated via traditional credentials (email/password), prompt them to link their Telegram account from inside their profile page rather than overwriting existing records automatically.
Need custom bot architectures or specialized backend integrations built for production workloads? BotCreator — studio that ships Telegram bots / Mini Apps.
Top comments (0)