-
-
Save konstantin24121/49da5d8023532d66cc4db1136435a885 to your computer and use it in GitHub Desktop.
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; | |
}; |
Object.entries(object).sort().map(([k, v]) => {
if (typeof v === "object" && v !== null) {
v = JSON.stringify(v);
}if (typeof v === "string" && /(https?:\/\/[^\s]+)/.test(v)) { v = v.replace(/\//g, "\\/"); } return `${k}=${v}`; }).join("\n");
Thanks a lot! It work like a charm!
Actual python implementation (for django).
def index(request):
tg_oauth_data = request.POST.dict()
try:
tg_oauth_data.pop('csrfmiddlewaretoken')
except KeyError:
return HttpResponseBadRequest()
data_check_string = '\n'.join(
f'{k}={v}' for k, v in sorted(tg_oauth_data.items())
if k != 'hash'
)
tg_bot_token = os.getenv('TG_BOT_TOKEN')
secret_key = hashlib.sha256(tg_bot_token.encode()).digest()
generated_hash = hmac.new(
secret_key,
data_check_string.encode(),
hashlib.sha256
).hexdigest()
authorization_is_valid = generated_hash == tg_oauth_data.get('hash')
# ...
Could you please provide nodejs example for .login() method not WebApp.
I use this but get an error with hashes
`import express from "express";
import crypto from "crypto";
import { fileURLToPath } from "url";
import path from "path";
import { config } from "../config/config.js";
const app = express();
const PORT = config.web.port;
const BOT_TOKEN = config.bot.token;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "public")));
/**
-
Validates the Telegram WebApp signature, ensuring proper URL escaping.
-
@param {string} botToken - The bot token
-
@param {string} telegramInitData - Query string from the request
-
@returns {boolean} - true if the signature is valid, otherwise false
*/
function validateTelegramHash(botToken, telegramInitData) {
try {
const initData = new URLSearchParams(telegramInitData);
initData.sort(); // 1️⃣ Sort parameters alphabeticallyconst receivedHash = initData.get("hash"); initData.delete("hash"); // 2️⃣ Remove hash before calculation // 3️⃣ Construct the data check string with proper escaping const dataToCheck = [...initData.entries()] .map(([key, value]) => { value = decodeURIComponent(value); // Escape slashes in URLs if (typeof value === "string" && /(https?:\/\/[^\s]+)/.test(value)) { value = value.replace(/\//g, "\\/"); } return `${key}=${value}`; }) .join("\n"); console.log("🔹 Data Check String:", dataToCheck); // 4️⃣ Generate secret key (SHA256 of the bot token with "WebAppData") const secretKey = crypto.createHmac("sha256", "WebAppData") .update(botToken) .digest(); // 5️⃣ Compute the hash const generatedHash = crypto.createHmac("sha256", secretKey) .update(dataToCheck) .digest("hex"); console.log("🔍 Generated Hash:", generatedHash); console.log("🔍 Received Hash:", receivedHash); return receivedHash === generatedHash; // 6️⃣ Compare hashes
} catch (error) {
console.error("❌ Error during validation:", error);
return false;
}
}
// Authentication route
app.get("/auth", (req, res) => {
console.log("🔹 Incoming parameters:", req.query);
const queryString = req.url.split("?")[1] || "";
if (!req.query.hash || !req.query.auth_date) {
console.error("❌ Error: Missing hash or auth_date parameters!");
return res.sendFile(path.join(__dirname, "public", "error.html"));
}
if (!validateTelegramHash(BOT_TOKEN, queryString)) {
console.error("❌ Error: Telegram signature is invalid!");
return res.sendFile(path.join(__dirname, "public", "error.html"));
}
console.log("✅ Authentication successful:", req.query);
res.sendFile(path.join(__dirname, "public", "auth.html"));
});
// Start server
app.listen(PORT, () => {
console.log(🚀 Server is running at ${config.web.url}
);
});
`
A Elixir example based on @TheBlackHacker python example.
defmodule TelegramValidator do
@telegram_bot_token "BOT_TOKEN"
@doc """
Verifies the authenticity of Telegram Web App data.
Takes a URL-encoded query string containing Telegram initialization data
and verifies its authenticity using HMAC-SHA256.
Returns a boolean indicating whether the data is valid.
"""
@spec verify_telegram_web_app_data(String.t()) :: boolean()
def verify_telegram_web_app_data(telegram_init_data) do
# Parse the query string
init_data = URI.decode_query(telegram_init_data)
# Get hash_value from the query string
hash_value = Map.get(init_data, "hash")
# Sort key-value pairs alphabetically, excluding the hash
data_to_check =
init_data
|> Map.drop(["hash"])
|> Enum.sort()
|> Enum.map(fn {key, value} -> "#{key}=#{value}" end)
|> Enum.join("\n")
# HMAC Calculation
secret =
:crypto.mac(:hmac, :sha256, "WebAppData", @telegram_bot_token)
calculated_hash =
:crypto.mac(:hmac, :sha256, secret, data_to_check)
|> Base.encode16(case: :lower)
calculated_hash == hash_value
end
end
# Test with the same data as in the Python version
test_data = "initdata"
IO.puts("Validation result: #{TelegramValidator.verify_telegram_web_app_data(test_data)}")
I released a tiny hex package for validation: https://hexdocs.pm/telegram_miniapp_validation/readme.html
For anyone implementing a new version, what I realized after several attempts that sorting the nested user fields is a problem, specifically if you parse it into an internal map / object and then create a String again.
https://github.com/Fuchsoria/telegram-webapps zero dependency golang package is now updated to actual state
Yes, after adding the photo_url parameter to initDataUnsafe, my code stopped working correctly. Here is its updated version:
Example of use