Last active
February 17, 2024 10:58
-
-
Save konstantin24121/49da5d8023532d66cc4db1136435a885 to your computer and use it in GitHub Desktop.
Telegram Bot 6.0 Validating data received via the Web App node implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const TELEGRAM_BOT_TOKEN = '110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw'; // https://core.telegram.org/bots#creating-a-new-bot | |
export const verifyTelegramWebAppData = async (telegramInitData: string): boolean => { | |
// The data is a query string, which is composed of a series of field-value pairs. | |
const encoded = decodeURIComponent(telegramInitData); | |
// HMAC-SHA-256 signature of the bot's token with the constant string WebAppData used as a key. | |
const secret = crypto | |
.createHmac('sha256', 'WebAppData') | |
.update(TELEGRAM_BOT_TOKEN); | |
// Data-check-string is a chain of all received fields'. | |
const arr = encoded.split('&'); | |
const hashIndex = arr.findIndex(str => str.startsWith('hash=')); | |
const hash = arr.splice(hashIndex)[0].split('=')[1]; | |
// sorted alphabetically | |
arr.sort((a, b) => a.localeCompare(b)); | |
// in the format key=<value> with a line feed character ('\n', 0x0A) used as separator | |
// e.g., 'auth_date=<auth_date>\nquery_id=<query_id>\nuser=<user> | |
const dataCheckString = arr.join('\n'); | |
// The hexadecimal representation of the HMAC-SHA-256 signature of the data-check-string with the secret key | |
const _hash = crypto | |
.createHmac('sha256', secret.digest()) | |
.update(dataCheckString) | |
.digest('hex'); | |
// if hash are equal the data may be used on your server. | |
// Complex data types are represented as JSON-serialized objects. | |
return _hash === hash; | |
}; |
Example with crypto-js
:
const CryptoJS = require("crypto-js")
export const verifyTelegramWebAppData = async (telegramInitData: string): Promise<boolean> => {
const initData = new URLSearchParams(telegramInitData);
const hash = initData.get("hash");
let dataToCheck: string[] = [];
initData.sort();
initData.forEach((val, key) => key !== "hash" && dataToCheck.push(`${key}=${val}`));
const secret = CryptoJS.HmacSHA256(TELEGRAM_BOT_TOKEN, "WebAppData");
const _hash = CryptoJS.HmacSHA256(dataToCheck.join("\n"), secret).toString(CryptoJS.enc.Hex);
return _hash === hash;
}
@TopYar great!
I added here package with golang implementation for verifying method https://github.com/Fuchsoria/telegram-webapps, feel free to test or use
@Fuchsoria thank you so much!
Thank you!
Example suggested by @TopYar wrapped in middleware for Nestjs/Fastify with nodejs native crypto
declarations.d.ts
declare module 'http' {
interface IncomingHttpHeaders {
'tg-init-data'?: string
}
}
check-init-data.middleware
import {Injectable, Logger, NestMiddleware, UnauthorizedException} from '@nestjs/common';
import {FastifyRequest, FastifyReply} from 'fastify';
import * as crypto from 'crypto';
const {
env: {
TELEGRAM_BOT_TOKEN
}
} = process;
@Injectable()
export class CheckInitDataMiddleware implements NestMiddleware {
private readonly logger: Logger = new Logger(CheckInitDataMiddleware.name);
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
this.logger.log(`Checking webapp init data`);
const {headers: {['tg-init-data']: tgInitData}} = req;
const initData = new URLSearchParams(tgInitData);
const hashFromClient = initData.get('hash');
const dataToCheck: string[] = [];
initData.sort();
initData.forEach((v, k) => k !== 'hash' && dataToCheck.push(`${k}=${v}`));
const secret = crypto
.createHmac('sha256', 'WebAppData')
.update(TELEGRAM_BOT_TOKEN);
const signature = crypto
.createHmac('sha256', secret.digest())
.update(dataToCheck.join('\n'));
const referenceHash = signature.digest('hex');
if (hashFromClient === referenceHash) {
return next();
}
throw new UnauthorizedException('Invalid init data');
}
}
I love you! 💘
Note that it seems to doesn't work when the user in telegram has a special character in its firstname or lastname.
Try it on your own :
- In telegram add '&' in your firstname or lastname
- try to connect on your webapp (with this auth mechanism)
You'll see that the hash generated by this method is different from the one generated by telegram
Fixed the problem with '&' not working correctly:
const verifyInitData = (telegramInitData: string): boolean => {
const urlParams = new URLSearchParams(telegramInitData);
const hash = urlParams.get('hash');
urlParams.delete('hash');
urlParams.sort();
let dataCheckString = '';
for (const [key, value] of urlParams.entries()) {
dataCheckString += `${key}=${value}\n`;
}
dataCheckString = dataCheckString.slice(0, -1);
const secret = crypto.createHmac('sha256', 'WebAppData').update(process.env.API_TOKEN ?? '');
const calculatedHash = crypto.createHmac('sha256', secret.digest()).update(dataCheckString).digest('hex');
return calculatedHash === hash;
}
@motsgar Thanks! <3
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here a Node implementation for Telegram Bot 6.0 Web App init data validation https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app