Uwierzytelnianie Telegram WebApp w JavaScript
Dzisiaj opowiem o skomplikowanym mechanizmie uwierzytelniania w Telegram WebApp Bot. Co to jest bot Telegram WebApp? To tylko możliwość uruchomienia WebView z twoją witryną w Telegramie. Możesz przeczytać więcej tutaj .
Dlaczego potrzebujemy tutaj autoryzacji?
Twoja witryna powinna być otwierana tylko w Telegram WebView. A co jeśli ktoś zrobiłby to w przeglądarce? „Hakerzy” mogą wykorzystywać fałszywe dane, identyfikatory i tak dalej. Musimy chronić nasze API przed osobami spoza Telegramu.
Jak?
Telegram wykorzystuje HMAC (kod uwierzytelniania wiadomości oparty na mieszaniu). Jeśli więc zainicjujesz Telegram SDK w swojej witrynie, będziesz mógł użyć tych danych do identyfikacji użytkownika. Przejdźmy do tworzenia mechanizmu autoryzacji krok po kroku.
Krok 1: przekazywanie danych przez żądania
Telegram SDK konfiguruje dane użytkownika w globalnym zasięgu Twojej aplikacji. Są tam dane o użytkownikach, ich smartfonach, motywach kolorystycznych i nie tylko. Możesz go znaleźć tutaj:
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;
Krok 2: tworzenie oprogramowania pośredniczącego Auth
W swoim projekcie używam Nest.js, ale sposób tworzenia oprogramowania pośredniczącego jest prawie taki sam w Express.js i Nest.js
Po pierwsze, powinniśmy stworzyć oprogramowanie pośredniczące za pomocą kilku linijek kodu:
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();
}
}
Krok 3: analizowanie pliku initData
Opiszę ten proces, a potem pokażę kod.
- Musimy przeanalizować łańcuch initData
- Weź pole skrótu z tego ciągu i zachowaj je na przyszłość
- Posortuj pozostałe pola według kolejności alfabetycznej
- Połącz te pola za pomocą łamacza wierszy (\n). Czemu? Właśnie dlatego! Telegram chce!
Spójrzmy na kod:
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'),
},
};
}
To już ostatni rozdział naszej podróży. Musimy przeanalizować initData przy użyciu funkcji z poprzedniego kroku i odrobiny kryptografii.
Powinniśmy podążać tą drogą:
- Napisz funkcję do kodowania wiadomości za pomocą algorytmu sh256 i klucza
- Przeanalizuj ciąg przy użyciu funkcji z poprzedniego kroku
- Utwórz tajny klucz, kodując Telegram Bot Token za pomocą klucza „WebAppData”.
- Utwórz skrót sprawdzania poprawności, kodując dataCheckString z poprzedniego pnia za pomocą tajnego klucza
- Porównaj hash sprawdzania poprawności z hashem z 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;
}
- Możesz dodać buforowanie, ponieważ kryptografia jest dość skomplikowaną rzeczą dla procesora, więc możesz użyć Redis lub nawet pamięci podręcznej w pamięci, aby na przykład zachować ciąg initData jak klucz i JSON userData jako wartość
- Możesz wygenerować swój własny token JWT raz po sprawdzeniu poprawności initData i możesz ustawić go w plikach cookie. Myślę, że jest to potężniejszy sposób tworzenia uwierzytelnienia.

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































