Telegram WebApp autenticación en JavaScript
Hoy voy a hablar sobre el complicado mecanismo de autenticación en Telegram WebApp Bot. ¿Qué es el bot de Telegram WebApp? Es solo la capacidad de ejecutar WebView con su sitio web dentro de Telegram. Puedes leer más aquí .
¿Por qué necesitamos autenticación aquí?
Se supone que su sitio web debe abrirse solo en Telegram WebView. Entonces, ¿qué pasaría si alguien hiciera esto en un navegador? Los "hackers" pueden usar datos de usuarios falsos, identificaciones, etc. Necesitamos proteger nuestra API de personas ajenas a Telegram.
¿Cómo?
Telegram utiliza HMAC (código de autenticación de mensajes basado en hash). Entonces, si inicializa Telegram SDK en su sitio web, podrá usar esos datos para identificar al usuario. Vamos a crear un mecanismo de autenticación paso a paso.
Paso 1: pasar datos a través de solicitudes
Telegram SDK configura los datos del usuario en el ámbito global de su aplicación. Hay datos sobre los usuarios, sus teléfonos inteligentes, temas de color y más. Lo puedes encontrar aquí:
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;
Paso 2: creación de middleware de autenticación
Uso Nest.js en mi proyecto, pero la forma de crear middleware es casi la misma en Express.js y Nest.js
En primer lugar, debemos crear el middleware con unas pocas líneas 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();
}
}
Paso 3: analizar el initData
Voy a describir el proceso y luego les mostraré el código.
- Necesitamos analizar la cadena initData
- Tome el campo hash de esa cadena y guárdelo para el futuro
- Ordenar el resto de los campos por orden alfabético
- Une estos campos usando un separador de línea (\n). ¿Por qué? ¡Simplemente porque! ¡Telegrama lo quiere!
Veamos el 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 es el último capítulo de nuestro viaje. Necesitamos analizar initData usando la función del paso anterior y un poco de criptografía.
Deberíamos seguir este camino:
- Escriba una función para codificar mensajes usando el algoritmo sh256 y una clave
- Analizar cadena usando la función del paso anterior
- Cree una clave secreta codificando Telegram Bot Token con la clave "WebAppData"
- Cree un hash de validación codificando dataCheckString del tallo anterior con una clave secreta
- Compara el hash de validación con el 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;
}
- Puede agregar el almacenamiento en caché porque la criptografía es algo bastante complicado para un procesador, por lo que puede usar Redis o incluso en memoria caché para mantener la cadena initData como una clave y userData JSON como un valor, por ejemplo.
- Puede generar su propio token JWT una vez después de validar initData y puede configurarlo en cookies. Creo que es una forma más poderosa de crear autenticación.

![¿Qué es una lista vinculada, de todos modos? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































