Autenticação do Telegram WebApp em JavaScript
Hoje vou falar sobre o complicado mecanismo de autenticação no Telegram WebApp Bot. O que é o bot do Telegram WebApp? É apenas uma capacidade de executar o WebView com seu site dentro do Telegram. Você pode ler mais aqui .
Por que precisamos de autenticação aqui?
Seu site deve ser aberto apenas no Telegram WebView. Então, e se alguém fizesse isso em um navegador? “Hackers” podem usar dados de usuários falsos, ids e assim por diante. Precisamos proteger nossa API de pessoas fora do Telegram.
Como?
O Telegram usa HMAC (código de autenticação de mensagem baseado em hash). Então, se você inicializar o SDK do Telegram em seu site, poderá usar esses dados para identificar o usuário. Vamos criar o mecanismo de autenticação passo a passo.
Etapa 1: passando dados por meio de solicitações
O Telegram SDK configura os dados do usuário no escopo global do seu aplicativo. Existem dados sobre usuários, seus smartphones, temas de cores e muito mais. Você pode encontrá-lo aqui:
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;
Etapa 2: criando o middleware Auth
Eu uso o Nest.js no meu projeto, mas a forma de criar middleware é quase a mesma no Express.js e no Nest.js
Primeiramente, devemos criar o middleware com algumas linhas de código:
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();
}
}
Etapa 3: analisando o initData
Vou descrever o processo e depois mostrarei o código.
- Precisamos analisar a string initData
- Pegue o campo hash dessa string e guarde-o para o futuro
- Classifique o restante dos campos por ordem alfabética
- Una esses campos usando o separador de linha (\n). Por quê? Só porque! Telegram quer!
Vejamos o código:
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'),
},
};
}
Este é o último capítulo da nossa jornada. Precisamos analisar initData usando a função da etapa anterior e um pouco de criptografia.
Devemos seguir este caminho:
- Escreva uma função para codificar mensagem usando o algoritmo sh256 e uma chave
- Analisar string usando a função da etapa anterior
- Crie uma chave secreta codificando o Telegram Bot Token com a chave “WebAppData”
- Crie um hash de validação codificando dataCheckString do tronco anterior com uma chave secreta
- Compare o hash de validação com o hash de 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;
}
- Você pode adicionar cache porque a criptografia é uma coisa muito complicada para um processador, portanto, você pode usar Redis ou até mesmo in-memory-cache para manter a string initData como uma chave e userData JSON como um valor, por exemplo
- Você pode gerar seu próprio token JWT uma vez após validar o initData e pode configurá-lo em cookies. É uma maneira mais poderosa de criar autenticação, eu acho.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)