Telegram. WebApp. Проверка подлинности пользователя. Не сходится хэш
Есть телеграмм-бот с веб-хуком кинутым на веб-сервер джанго. Есть веб-приложение телеграмма, которое при инициации вызывает вьюху джанги и возвращает шаблон. В этом шаблоне, согласно документации телеграма, в head вписан скрипт
<script src="https://telegram.org/js/telegram-web-app.js"></script>
И далее в body мне нужно проверить подлинность пользователя телеграмма. Я следовал документации о проверке данных и советам из инета и в шаблоне прописал следующий скрипт:
<script>
const WebApp = window.Telegram.WebApp;
let initDataURLSP = new URLSearchParams(WebApp.initData);
var hash = initDataURLSP.get('hash');
initDataURLSP.delete('hash');
initDataURLSP.sort();
var checkDataString = initDataURLSP.toString().replaceAll('&', '\n');
let data_send = {
'hash': hash,
'checkDataString': checkDataString,
};
fetch(
'https://address/backend/check_hash/',
{
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(data_send),
}
);
</script>
На бэке это принимается вьюхой:
@csrf_exempt
def check_hash(request: HttpRequest):
request_dict = json.loads(request.body)
data_check_string = request_dict['checkDataString']
tg_hash = request_dict['hash']
import hashlib, hmac, os
from dotenv import load_dotenv
load_dotenv()
token = os.getenv('TOKEN_DJEM_DEV_BOT')
secret_key = hmac.new(
key="WebAppData".encode(),
msg=token.encode(),
digestmod=hashlib.sha256
).digest()
local_hash = hmac.new(
key=secret_key,
msg=data_check_string.encode(),
digestmod=hashlib.sha256
).hexdigest()
check = tg_hash == local_hash
... # Дальше идёт возврат хттп-ответа.
Так вот, check у меня приходит False и tg_hash != local_hash
Пробовал присылать неизменённую initData на бэк:
const WebApp = window.Telegram.WebApp;
let data_send = {
'checkDataString': WebApp.initData,
};
fetch(
'https://address/backend/poll_data/',
{
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(data_send),
}
);
и уже там ещё форматировать:
data_check_string = request_dict['checkDataString']
data_check_string_list: list = data_check_string.split('&')
tg_hash = data_check_string_list.pop(-1)[5:]
data_check_string_list.sort()
data_check_string = '\n'.join(data_check_string_list)
Результат неотличим.
Как пройти проверку пользователя средствами python и js?
Ответы (2 шт):
Нашёл решение на просторах Интернета. Там код на пайтоне из библиотеки aiogram. Среди прочего есть функция, которая принимает токен бота и init_data (которую присылает веб-приложение с фронта) и возвращает булево значение, соответствующее подлинности пользователя:
import hashlib
import hmac
from operator import itemgetter
from urllib.parse import parse_qsl
def check_webapp_signature(token: str, init_data: str) -> bool:
"""
Check incoming WebApp init data signature
Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
:param token:
:param init_data:
:return:
"""
try:
parsed_data = dict(parse_qsl(init_data))
except ValueError:
# Init data is not a valid query string
return False
if "hash" not in parsed_data:
# Hash is not present in init data
return False
hash_ = parsed_data.pop('hash')
data_check_string = "\n".join(
f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0))
)
secret_key = hmac.new(
key=b"WebAppData", msg=token.encode(), digestmod=hashlib.sha256
)
calculated_hash = hmac.new(
key=secret_key.digest(), msg=data_check_string.encode(), digestmod=hashlib.sha256
).hexdigest()
return calculated_hash == hash_
Пока не разобрался почему мой код не работал, а этот работает, но вопрос решён.
Я столкнулся с такой же проблемой. Исходя из кода python который ты приложил выше, я реализовал это на TS.
Надеюсь кому-то помогу в поисках.
import crypto from 'crypto';
const checkWebAppSignature = (token: string, initData: string): boolean => {
/**
* Проверяет валидность подписи данных от Telegram WebApp
*
* Источник:
https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
*
* @param token Токен бота
* @param initData Данные, полученные от WebApp
* @returns true, если подпись валидна, иначе false
*/
try {
const parsedData = Object.fromEntries(new URLSearchParams(initData));
if (!parsedData.hash) {
// Хэш отсутствует в данных
return false;
}
// Извлечение хэша
const receivedHash = parsedData.hash;
delete parsedData.hash;
// Формируем строку для проверки подписи
const dataCheckString = Object.keys(parsedData)
.sort()
.map((key) => `${key}=${parsedData[key]}`)
.join('\n');
// Генерируем секретный ключ
const secretKey = crypto
.createHmac('sha256', 'WebAppData')
.update(token)
.digest();
// Вычисляем хэш
const calculatedHash = crypto
.createHmac('sha256', secretKey)
.update(dataCheckString)
.digest('hex');
// Сравниваем полученный хэш и вычисленный
return calculatedHash === receivedHash;
} catch (error) {
console.error('Ошибка при разборе initData:', error);
return false;
}
};
// Пример использования
const initData = 'query_id=... &user=first_name... и т.д. это нормально';
const token = '77....01:AAH....X';
console.log(checkWebAppSignature(token, initData));
Кратко расскажу логику валидации: Пользователь при подключении выдает свои данные с хешом. Если объединить эти данные, вместе с вашим botApiKey, вы сможете проверить все данные которые сообщил пользователь.