Skip to content

Instantly share code, notes, and snippets.

@konstantin24121
Last active July 22, 2024 01:42
Show Gist options
  • Save konstantin24121/49da5d8023532d66cc4db1136435a885 to your computer and use it in GitHub Desktop.
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
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;
};
@Matevos7
Copy link

NestJS people

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
import TelegramBot from 'node-telegram-bot-api';

function verifyInitData(telegramInitData: string, botToken: string): { isVerified: boolean, urlParams: URLSearchParams } {
  const urlParams: URLSearchParams = 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(botToken);
  const calculatedHash = crypto.createHmac('sha256', secret.digest()).update(dataCheckString).digest('hex');

  const isVerified = calculatedHash === hash;

  return { isVerified, urlParams };
}

@Injectable()
export class ValidateTelegramDataMiddleware implements NestMiddleware {
  use(req: Request & { user: any }, res: Response, next: NextFunction) {
    const telegramInitData = ((req.headers.initdata ?? req.query.initData ?? req.query.initdata) as string);
    const botToken = process.env.TELEGRAM_TOKEN;

    if (!telegramInitData || !botToken) {
      return res.status(400).send('Invalid request');
    }

    const { urlParams, isVerified } = verifyInitData(telegramInitData, botToken);

    if (!isVerified) {
      return res.status(403).send('Unauthorized request');
    }

    const user: TelegramBot.User = typeof urlParams.get('user') === 'string' ? JSON.parse(urlParams.get('user')) : urlParams.get('user');

    req.user = user;

    next();
  }
}

@S0mbre
Copy link

S0mbre commented Jul 8, 2024

Python impl here ))

import hmac

BOT_TOKEN = 'my-bot-token'

def hmac_256(key: str | bytes, value: str | bytes, as_hex: bool = False) -> str | bytes:
    """Makes HMAX digest of key, value as bytes or a hex string"""
    if isinstance(key, str):
        key = key.encode()
    if isinstance(value, str):
        value = value.encode()
    if as_hex: return hmac.new(key, value, 'sha256').hexdigest()
    return hmac.digest(key, value, 'sha256')

def hmac_validate(digest1: str | bytes, digest2: str | bytes) -> bool:
    """Validates a pair of HMAC hashes - must use this instead of simple == for security reasons!"""
    if type(digest1) != type(digest2): return False
    return hmac.compare_digest(digest1, digest2)

def validate_web_app(initdata: str) -> bool:
    # see https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
    their_hash = None
    vals = sorted(initdata.split('&'))
    for val in vals:
        if val.startswith('hash='):
            their_hash = val.split('=')[1].strip() or None
            vals.remove(val)
            break
    if not their_hash: return False
    initdata = '\n'.join(vals)
    secret_key = hmac_256('WebAppData', BOT_TOKEN)
    my_hash = hmac_256(secret_key, initdata, True)
    return hmac_validate(my_hash, their_hash)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment