Аутентификация Telegram WebApp на JavaScript
Сегодня я расскажу о хитром механизме аутентификации в Telegram WebApp Bot. Что такое бот Telegram WebApp? Это просто возможность запустить WebView с вашим сайтом внутри Telegram. Вы можете прочитать больше здесь .
Зачем нам здесь авторизация?
Ваш сайт должен открываться только в Telegram WebView. Итак, что, если бы кто-то сделал это в браузере? «Хакеры» могут использовать поддельные пользовательские данные, идентификаторы и так далее. Нам нужно защитить наш API от людей за пределами Telegram.
Как?
Telegram использует HMAC (код аутентификации сообщений на основе хэша). Итак, если вы инициализируете Telegram SDK на своем веб-сайте, вы сможете использовать эти данные для идентификации пользователя. Давайте шаг за шагом создадим механизм аутентификации.
Шаг 1: передача данных через запросы
Telegram SDK устанавливает данные пользователя в глобальную область вашего приложения. Есть данные о пользователях, их смартфонах, цветовых темах и многом другом. Вы можете найти это здесь:
window.Telegram.WebApp
const { initData } = window.Telegram.WebApp
auth_date=<auth_date>&query_id=<query_id>&user=<user>&hash=<hash>
axios.defaults.headers.common['Telegram-Data'] = window?.Telegram?.WebApp?.initData;
Шаг 2: создание промежуточного ПО аутентификации
Я использую Nest.js в своем проекте, но способ создания промежуточного ПО почти одинаков в Express.js и Nest.js.
Во-первых, мы должны создать промежуточное ПО с помощью нескольких строк кода:
export function telegramAuthMiddleware(req, res, next) {
// take initData from headers
const iniData = req.headers[
'telegram-data'
];
// use our helpers (see bellow) to validate string
// and get user from it
const user = checkAuthorization(iniData);
// add uses to the request "context" for the future
if (user) {
req.user = user;
next();
// or if the validation is failed response 401
} else {
res.writeHead(401, { 'content-type': 'application/json' });
res.write('unauthorized');
res.end();
}
}
Шаг 3: анализ initData
Я собираюсь описать процесс, а затем покажу вам код.
- Нам нужно разобрать строку initData
- Возьмите хеш - поле из этой строки и сохраните его на будущее.
- Отсортируйте остальные поля в алфавитном порядке
- Соедините эти поля с помощью разрыва строки (\n). Почему? Да просто так! Телеграм хочет!
Давайте посмотрим на код:
function parseAuthString(iniData) {
// parse string to get params
const searchParams = new URLSearchParams(iniData);
// take the hash and remove it from params list
const hash = searchParams.get('hash');
searchParams.delete('hash');
// sort params
const restKeys = Array.from(searchParams.entries());
restKeys.sort(([aKey, aValue], [bKey, bValue]) => aKey.localeCompare(bKey));
// and join it with \n
const dataCheckString = restKeys.map(([n, v]) => `${n}=${v}`).join('\n');
return {
dataCheckString,
hash,
// get metaData from params
metaData: {
user: JSON.parse(searchParams.get('user')),
auth_date: searchParams.get('auth_date'),
query_id: searchParams.get('query_id'),
},
};
}
Это последняя глава нашего путешествия. Нам нужно разобрать initData, используя функцию из предыдущего шага и немного криптографии.
Мы должны идти по этому пути:
- Напишите функцию для кодирования сообщения с использованием алгоритма sh256 и ключа
- Разобрать строку, используя функцию из предыдущего шага
- Создайте секретный ключ, закодировав Telegram Bot Token с помощью ключа «WebAppData».
- Создайте хэш проверки, закодировав dataCheckString из предыдущей основы с помощью секретного ключа.
- Сравните хеш проверки с хэшем из initData
const crypto = require('crypto')
const WEB_APP_DATA_CONST = "WebAppData"
const TELEGRAM_BOT_TOKEN = "so secret token!!"
// encoding message with key
// we need two types of representation here: Buffer and Hex
function encodeHmac(message, key, repr=undefined) {
return crypto.createHmac('sha256', key).update(message).digest(repr);
}
function checkAuthorization(iniData){
// parsing the iniData sting
const authTelegramData = parseAuthString(iniData);
// creating the secret key and keep it as a Buffer (important!)
const secretKey = encodeHmac(
TELEGRAM_BOT_TOKEN,
WEB_APP_DATA_CONST,
);
// creating the validation key (and transform it to HEX)
const validationKey = encodeHmac(
authTelegramData.dataCheckString,
secretKey,
'hex',
);
// the final step - comparing and returning
if (validationKey === authTelegramData.hash) {
return authTelegramData.metaData.user;
}
return null;
}
const crypto = require('crypto')
const WEB_APP_DATA_CONST = "WebAppData"
const TELEGRAM_BOT_TOKEN = "so secret token!!"
export function telegramAuthMiddleware(req, res, next) {
// take initData from headers
const iniData = req.headers[
'telegram-data'
];
// use our helpers (see bellow) to validate string
// and get user from it
const user = checkAuthorization(iniData);
// add uses to the request "context" for the future
if (user) {
req.user = user;
next();
// or if the validation is failed response 401
} else {
res.writeHead(401, { 'content-type': 'application/json' });
res.write('unauthorized');
res.end();
}
}
function parseAuthString(iniData) {
// parse string to get params
const searchParams = new URLSearchParams(iniData);
// take the hash and remove it from params list
const hash = searchParams.get('hash');
searchParams.delete('hash');
// sort params
const restKeys = Array.from(searchParams.entries());
restKeys.sort(([aKey, aValue], [bKey, bValue]) => aKey.localeCompare(bKey));
// and join it with \n
const dataCheckString = restKeys.map(([n, v]) => `${n}=${v}`).join('\n');
return {
dataCheckString,
hash,
// get metaData from params
metaData: {
user: JSON.parse(searchParams.get('user')),
auth_date: searchParams.get('auth_date'),
query_id: searchParams.get('query_id'),
},
};
}
// encoding message with key
// we need two types of representation here: Buffer and Hex
function encodeHmac(message, key, repr=undefined) {
return crypto.createHmac('sha256', key).update(message).digest(repr);
}
function checkAuthorization(iniData){
// parsing the iniData sting
const authTelegramData = parseAuthString(iniData);
// creating the secret key and keep it as a Buffer (important!)
const secretKey = encodeHmac(
TELEGRAM_BOT_TOKEN,
WEB_APP_DATA_CONST,
);
// creating the validation key (and transform it to HEX)
const validationKey = encodeHmac(
authTelegramData.dataCheckString,
secretKey,
'hex',
);
// the final step - comparing and returning
if (validationKey === authTelegramData.hash) {
return authTelegramData.metaData.user;
}
return null;
}
- Вы можете добавить кеширование, потому что криптография — довольно сложная вещь для процессора, поэтому вы можете использовать Redis или даже кэш в памяти, чтобы сохранить строку initData, например ключ, и userData JSON в качестве значения, например.
- Вы можете сгенерировать свой собственный токен JWT один раз после проверки initData и установить его в файлы cookie. Я думаю, что это более мощный способ создания аутентификации.

![В любом случае, что такое связанный список? [Часть 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































