Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. initData imzosi
- 2.1 Imzo qanday yasaladi
- 2.2 Jonli: Node'da tekshirish
- 3. Frontend: initData bilan kirish
- 3.1 Sarlavhada yuborish
- 3.2 Uzoq ochiq turgan ilova
- 4. startapp: kerakli sahifada ochish
- 4.1 Havola va parametr
- 4.2 Jonli: parametrdan yo'l
- 5. To'lov: Telegram Stars
- 5.1 Qaysi to'lov qachon
- 5.2 Stars oqimi
- 5.3 Jonli: openInvoice
- 6. CloudStorage: foydalanuvchi sozlamalari
- 6.1 Nima va qachon
- 6.2 Jonli: callback'ni Promise qilish
- 7. Production: ekran, sinov, tunnel
- 7.1 To'liq ekran va xavfsiz zona
- 7.2 Lokal sinash: HTTPS tunnel
- 7.3 Telegram Login Widget (oddiy sayt uchun)
- 7.4 Chiqarishdan oldin
- 7.5 Nega vazifalar da yo'q
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): data-check-string
- 3-mashq (qiyin): useTelegramSession — qayta urinish bilan
- 4-mashq: tajriba qadami (ixtiyoriy)
- 11. Real ishda
- Xulosa
- Manbalar
Telegram Mini Apps auth va to'lov: initData imzosi, startapp, Stars, CloudStorage va production
Qisqacha: Mini App foydalanuvchini o'zi "tanimaydi": u xom
initDatasatrini serverga yuboradi, server esa Telegram imzosini bot tokeni bilan tekshiradi (HMAC-SHA256, kalit —"WebAppData") vaauth_dateeskirmaganini ko'radi. Shundan keyinginauser.idga ishoniladi.t.me/<bot>/<ilova>?startapp=taom-42— ilovani kerakli sahifada ochadi (start_param, tekshirib). Raqamli mahsulot uchun to'lov — Telegram Stars: servercreateInvoiceLink(valyutaXTR), frontendopenInvoice, tasdiq — bot serveriga.CloudStorage— foydalanuvchi uchun 1024 tagacha kalit. Lokal sinov — HTTPS tunnel yoki test serveri. Bot va token biz yaratmadik: imzo tekshiruvi Node'da soxta token bilan jonli.
Bu darsda
initDataimzosini Node'da tekshirasiz: data-check-string, maxfiy kalit,hash,auth_date, soxtalashtirishga urinish.- Frontend'dan
initDatani sarlavhada yuborib, server sessiyasini olasiz. startapphavolasidan sahifa yo'lini xavfsiz chiqarasiz.- Telegram Stars to'lov oqimini (
createInvoiceLink→openInvoice→pre_checkout_query→successful_payment) tushuntirasiz. CloudStorage, to'liq ekran va xavfsiz zona, test serveri, HTTPS tunnel va Telegram Login Widget bilan ishlaysiz.
Oldin bilishingiz kerak: Telegram Mini Apps I, To'lov UI, Dev server proxy, HTTPS va CORS, React'da auth holati, URLSearchParams.
1. Nega bu kerak?
O'tgan darsda «Bahor» menyusi Telegram ichida ochildi: ism, tema, ko'k tugma. Endi uchta haqiqiy savol:
- Mehmon buyurtma berdi. Server uni kimning nomiga yozadi?
initDataUnsafe.user.idni hujumchi o'zgartira oladi — buni ko'rdik. - Jasur aka kanalga "Bugun manti 10% arzon" postini qo'ydi. Tugma Mini App'ni bosh sahifada emas, aynan manti sahifasida ochishi kerak.
- «Bahor» elektron retseptlar kitobini sotmoqchi. Telegram ichida qanday to'lov olinadi?
Bu darsning javoblari: imzo tekshiruvi, startapp havolasi va Telegram Stars. Yana — production'ga chiqish uchun kerakli narsalar: foydalanuvchi sozlamalarini saqlash, to'liq ekran, lokal sinash.
Hayotiy o'xshatish: konvertdagi muhr. Telegram mehmon haqidagi ma'lumotni (initData) konvertga solib, muhr (hash) bosadi. Muhrni faqat Telegram va bot egasi biladigan "pechat" (bot tokeni) bilan yasash mumkin. Konvert mehmonning qo'lidan o'tadi — u ichini o'zgartirsa, muhr mos kelmay qoladi. Server konvertni ochishdan oldin muhrni tekshiradi.
2. initData imzosi
2.1 Imzo qanday yasaladi
Telegram hujjati (core.telegram.org/bots/webapps, "Validating data received via the Mini App") algoritmni shunday beradi:
initDatadagi barcha maydonlar (hashdan tashqari) alifbo tartibida,kalit=qiymatko'rinishida,\nbilan qo'shiladi — bu data-check-string.- Maxfiy kalit:
HMAC_SHA256(bot_token, "WebAppData")—"WebAppData"kalit sifatida, bot tokeni — xabar. hex(HMAC_SHA256(data_check_string, maxfiy_kalit))—hashga teng bo'lsa, ma'lumot Telegram'dan.
HMAC (hash-based message authentication code) — xabar va maxfiy kalitdan yasaladigan "muhr". Kalitni bilmasdan to'g'ri HMAC yasash amalda mumkin emas. Kalitni bilgan esa xabarning bitta harfi o'zgarganini ham sezadi. To'liq — crypto: hash va HMAC darsida.
flowchart LR
A["initData<br/>(hash'siz)"] --> B["tartiblash<br/>kalit=qiymat"]
T["bot tokeni"] --> K["HMAC('WebAppData',<br/>token)"]
B --> H["HMAC(maxfiy kalit,<br/>data-check-string)"]
K --> H
H --> C{"= hash ?"}
C -- ha --> OK["ishonamiz"]
C -- yo'q --> NO["rad"]Nimaga qarang: bot tokeni faqat serverda ishlatiladi. Brauzerga u hech qachon kelmaydi — aks holda har kim imzo yasay olardi.
2.2 Jonli: Node'da tekshirish
Bu — backend kodi (Node, node:crypto). Token soxta — biz bot yaratmadik. Lekin algoritm haqiqiy: Telegram'ning haqiqiy initData si haqiqiy token bilan xuddi shunday tekshiriladi. To'liq backend — Telegram Login va initData tekshiruvi darsida.
import { createHmac, timingSafeEqual } from "node:crypto";
// SOXTA token — faqat dars uchun. Haqiqiysi: process.env.BOT_TOKEN
const BOT_TOKEN = "123456:SOXTA-TOKEN-faqat-dars-uchun";
function sign(fields, token) {
const dataCheck = Object.keys(fields)
.sort()
.map((key) => `${key}=${fields[key]}`)
.join("\n");
const secret = createHmac("sha256", "WebAppData")
.update(token)
.digest();
return createHmac("sha256", secret).update(dataCheck).digest("hex");
}
function validate(initData, token, maxAgeSec, now) {
const params = new URLSearchParams(initData);
const hash = params.get("hash");
if (!hash) return "hash yo'q";
params.delete("hash");
const fields = Object.fromEntries(params);
const expected = sign(fields, token);
const same =
hash.length === expected.length &&
timingSafeEqual(Buffer.from(hash), Buffer.from(expected));
if (!same) return "imzo noto'g'ri";
if (now - Number(fields.auth_date) > maxAgeSec) return "eskirgan";
return "to'g'ri: " + JSON.parse(fields.user).first_name;
}
// Telegram'ning ishini taqlid qilamiz: imzolangan initData
const fields = {
auth_date: "1760000000",
query_id: "AAH-sinov",
user: JSON.stringify({ id: 1, first_name: "Aziz" }),
};
const initData = new URLSearchParams({
...fields,
hash: sign(fields, BOT_TOKEN),
}).toString();
const now = 1760000000 + 60; // bir daqiqadan keyin
console.log(validate(initData, BOT_TOKEN, 3600, now));
const forged = initData.replace("Aziz", "Jasur");
console.log(validate(forged, BOT_TOKEN, 3600, now));
console.log(validate(initData, BOT_TOKEN, 3600, now + 86400));
console.log(validate(initData, "999:BOSHQA-BOT", 3600, now));Konsolda:
to'g'ri: Aziz
imzo noto'g'ri
eskirgan
imzo noto'g'riTo'rt holat:
- Asl
initData— imzo mos, bir daqiqa oldin ochilgan: ishonamiz. - "Aziz" → "Jasur" — hujumchi ismni (yoki
idni) o'zgartirdi. Imzo endi mos emas. - Bir kundan keyin — imzo to'g'ri, lekin
auth_dateeski. Kimdir eskiinitDatani ushlab olib, qayta ishlatmoqda (replay — qayta o'ynatish hujumi). Muddat (bu yerda 1 soat) — sizning qaroringiz. - Boshqa botning tokeni — boshqa botning Mini App'idan olingan to'g'ri
initDatasizning botingizda o'tmaydi.
Ikki kichik, lekin muhim detal:
timingSafeEqual— oddiy===satrlarni birinchi farq qilgan belgigacha solishtiradi: javob vaqti "nechta belgi to'g'ri" ekanini sizdiradi.timingSafeEqualhar doim to'liq solishtiradi. Uzunlik oldin tekshiriladi — u teng bo'lmagan buferlarda xato tashlaydi.auth_date— soniyada (Unix vaqti),Date.now()esa millisoniyada. Haqiqiy koddaMath.floor(Date.now() / 1000).
Bot API 8.0 dan initData da signature maydoni ham bor — bot tokenisiz, Telegram'ning ochiq kaliti (Ed25519) bilan tekshirish uchun. U uchinchi tomon (bot egasi bo'lmagan xizmat) uchun. O'z botingizda hash yetarli; signature boshqa maydonlar qatorida data-check-string'ga kiradi.
Tekshirib ko'ring: Dasturchi tezlik uchun imzoni brauzerda tekshirishni xohlaydi: "SubtleCrypto bor, tokenni
VITE_BOT_TOKENga qo'yamiz". Nima bo'ladi?
Javob
Token build'ga, ya'ni har bir foydalanuvchining brauzeriga tushadi. Hujumchi uni o'qiydi va istalgan initData ga to'g'ri imzo yasaydi — tekshiruv ma'nosini yo'qotadi. Bundan ham yomoni: bot tokeni bilan botning o'zini boshqarish mumkin (xabar yuborish, webhook o'zgartirish). Bot tokeni — faqat serverda, VITE_ prefiksi bilan hech qachon (env o'zgaruvchilar).
3. Frontend: initData bilan kirish
3.1 Sarlavhada yuborish
Frontend'ning vazifasi oddiy: xom initData ni har so'rov bilan (yoki bir marta — sessiya olish uchun) serverga yuborish. Keng tarqalgan kelishuv (@tma.js hujjatidagi) — Authorization sarlavhasi tma sxemasi bilan: Authorization: tma <initData>. Server har so'rovda imzoni tekshiradi yoki birinchi marta tekshirib, o'z sessiya cookie'sini beradi (React'da auth holati).
import { useEffect, useState } from "react";
// O'quv modeli: Telegram bergan xom satr va soxta server
const initData =
"user=%7B%22id%22%3A1%2C%22first_name%22%3A%22Aziz%22%7D" +
"&auth_date=1760000000&hash=…";
function api(path, { headers }) {
const auth = headers.Authorization;
console.log(path, "→", auth.slice(0, 15) + "…");
return new Promise((resolve) =>
setTimeout(() => {
// server imzoni tekshirdi (yuqoridagi validate) va javob berdi
resolve({ ok: true, user: { id: 1, name: "Aziz" } });
}, 200),
);
}
function useTelegramSession() {
const [state, setState] = useState({ status: "loading" });
useEffect(() => {
if (!initData) {
setState({ status: "outside" }); // Telegram'dan tashqarida
return;
}
api("/api/sessiya", {
headers: { Authorization: `tma ${initData}` },
}).then((res) =>
setState(
res.ok
? { status: "ok", user: res.user }
: { status: "denied" },
),
);
}, []);
return state;
}
export default function App() {
const session = useTelegramSession();
if (session.status === "loading") return <p>Kirilmoqda…</p>;
if (session.status === "outside")
return <p>Ilovani Telegram orqali oching.</p>;
if (session.status === "denied") return <p>Kirish rad etildi.</p>;
return (
<p>Xush kelibsiz, {session.user.name}! (server tasdiqladi)</p>
);
}Konsolda:
/api/sessiya → tma user=%7B%22…Ekranda:
Xush kelibsiz, Aziz! (server tasdiqladi)Endi ekrandagi ism — serverning javobidan, initDataUnsafe dan emas. Server buyurtmani ham, ballni ham shu tasdiqlangan id bilan yozadi.
Uchta holat ham ko'zda tutilgan: ichida (ok), tashqarida (outside — oddiy brauzerda ochilgan, initData bo'sh), rad (denied — imzo yoki muddat o'tmadi). Rad holatida "Ilovani qaytadan oching" — initData eskirgan bo'lishi mumkin: mehmon Mini App'ni kecha ochib, yopmagan.
3.2 Uzoq ochiq turgan ilova
auth_date — Mini App ochilgan vaqt. Mehmon ilovani ochib, ikki soat ishlatsa, initData hamon o'sha. Agar server har so'rovda "1 soatdan eski — rad" desa, ikkinchi soatda ilova "buziladi". Odatiy yechim: birinchi so'rovda initData tekshiriladi va server o'z sessiya tokenini (cookie) beradi. Keyingi so'rovlar — sessiya bilan. initData muddati faqat sessiyani ochishda tekshiriladi.
4. startapp: kerakli sahifada ochish
4.1 Havola va parametr
Kanal postidagi havola: https://t.me/<bot>/<ilova>?startapp=taom-42. Telegram Mini App'ni ochadi va startapp qiymatini beradi:
WebApp.initDataUnsafe.start_param—"taom-42";- manzilda
tgWebAppStartParam=taom-42(hujjat bo'yicha — birinchi chizishdayoq to'g'ri sahifani ko'rsatish uchun).
Rasmiy hujjat (2026-10 holatiga) qiymat uchun belgi va uzunlik chegarasini aniq yozmaydi. Jamoa hujjati (docs.telegram-mini-apps.com) 64 belgigacha va faqat lotin harflari, raqamlar va _ deydi; bot havolasidagi start uchun Bot API - ga ham ruxsat beradi. Xavfsiz yo'l — 64 belgigacha, faqat A-Z a-z 0-9 _ -. Ya'ni /taom/42 kabi yo'lni to'g'ridan yozib bo'lmaydi — o'z "kodlash" ingizni o'ylab topasiz.
4.2 Jonli: parametrdan yo'l
start_param — foydalanuvchi o'zi yozishi mumkin bo'lgan tashqi qiymat (URL'dagi qiymatni tekshirish). Shuning uchun — faqat ma'lum shakllar, qolgani — bosh sahifa:
const RULES = [
// taom-42 → /taom/42
[/^taom-(\d{1,6})$/, (m) => `/taom/${m[1]}`],
// chegirma-BAHOR10 → /?kod=BAHOR10
[/^chegirma-([A-Z0-9]{3,12})$/, (m) => `/?kod=${m[1]}`],
// bolim-ichimliklar → /bolim/ichimliklar
[/^bolim-(osh|ichimliklar|shirinlik)$/, (m) => `/bolim/${m[1]}`],
];
function routeFromStartParam(param) {
if (typeof param !== "string" || param.length > 64) return "/";
for (const [pattern, toPath] of RULES) {
const m = param.match(pattern);
if (m) return toPath(m);
}
return "/";
}
for (const p of [
"taom-42",
"chegirma-BAHOR10",
"bolim-ichimliklar",
"bolim-admin",
"taom-42/../../admin",
undefined,
]) {
console.log(String(p), "→", routeFromStartParam(p));
}Konsolda:
taom-42 → /taom/42
chegirma-BAHOR10 → /?kod=BAHOR10
bolim-ichimliklar → /bolim/ichimliklar
bolim-admin → /
taom-42/../../admin → /
undefined → /- Har qoida —
^…$bilan: butun satr mos kelishi kerak, "ichida bor" yetmaydi (langarlar). - Bo'limlar — ro'yxat (
osh|ichimliklar|shirinlik), "istalgan so'z" emas:bolim-admino'tmaydi. - Natija — o'zimiz yasagan yo'l. Router'ga
start_paramning o'zi berilmaydi.
React Router'da buni birinchi chizishdan oldin qilish qulay: createBrowserRouter ga initialEntries emas, ilova ochilganda bir marta navigate(routeFromStartParam(…), { replace: true }) (dasturiy navigatsiya). replace — "orqaga" bosilganda bo'sh sahifaga qaytmasin.
Bundan tashqari bot o'zi ham havola yasashi mumkin: t.me/<bot>?start=… — bu Mini App emas, bot chati /start buyrug'i bilan ochiladi. Ikkalasini adashtirmang: Mini App uchun — startapp.
5. To'lov: Telegram Stars
5.1 Qaysi to'lov qachon
Telegram ichida ikki xil narsa sotiladi:
- Raqamli mahsulot va xizmat (elektron kitob, kurs, o'yin ichidagi narsa, premium funksiya) — Telegram'ning "Bot Payments API for Digital Goods" sahifasiga ko'ra (2026-10 holatiga) faqat Telegram Stars (yulduzlar) bilan. Sababi — Apple va Google do'kon qoidalari: Telegram ilovasi ichidagi raqamli savdo ularning to'lov tizimidan o'tishi kerak.
- Jismoniy mahsulot (osh yetkazib berish) — boshqa to'lov provayderlari bilan: Telegram Payments (BotFather'da ulanadigan provayderlar) yoki o'tgan darsdagi kabi o'z checkout'ingiz.
Qoidalar o'zgarib turadi — loyihadan oldin Telegram'ning "Bot Developers" shartlarini o'qing.
5.2 Stars oqimi
sequenceDiagram
participant W as Mini App
participant S as Bot serveri
participant T as Telegram
W->>S: "retseptlar kitobi" ni sotib olaman
S->>T: createInvoiceLink (XTR, 50 yulduz)
T-->>S: invoice havolasi
S-->>W: havola
W->>T: WebApp.openInvoice(havola)
T->>S: pre_checkout_query
S->>T: answerPreCheckoutQuery (ok)
T->>S: successful_payment
T-->>W: callback("paid")
W->>S: polling: buyurtma holati?Nimaga qarang: tanish rasm. Invoice'ni server yaratadi (narx serverda), to'lov tasdig'i serverga keladi (successful_payment), frontend esa faqat oynani ochadi va natijani so'raydi. Bu — o'tgan darsdagi Payme/Click oqimining Telegram varianti.
Backend tomoni (hujjat bo'yicha): createInvoiceLink da currency: "XTR", narx — yulduzlar soni (butun son), provider_token — bo'sh. pre_checkout_query ga bot 10 soniya ichida javob berishi kerak — aks holda to'lov bekor bo'ladi. Qaytarish — refundStarPayment. Bular — Mini App backendi va Stars darsida.
5.3 Jonli: openInvoice
Frontend tomoni — bitta metod va callback. status — "paid", "cancelled", "failed" yoki "pending":
import { useState } from "react";
// O'quv modeli: Telegram to'lov oynasi o'rnida —
// 200 ms dan keyin "paid"
const WebApp = {
openInvoice(url, callback) {
console.log("invoice:", url);
setTimeout(() => callback("paid"), 200);
},
};
function createInvoice() {
return Promise.resolve("https://t.me/$sinov-invoice");
}
const TEXT = {
idle: "",
opening: "To'lov oynasi ochilmoqda…",
paid: "To'landi! Kitob tasdiqlanishi bilan ochiladi.",
cancelled: "To'lov bekor qilindi.",
failed: "To'lov o'tmadi. Qayta urinib ko'ring.",
pending: "To'lov kutilmoqda…",
};
export default function App() {
const [status, setStatus] = useState("idle");
async function buy() {
setStatus("opening");
const link = await createInvoice(); // serverdan
WebApp.openInvoice(link, (result) => {
console.log("natija:", result);
setStatus(result);
// "paid" — faqat xabar. Kitobni server tasdig'idan keyin
// ochamiz (successful_payment → polling)
});
}
return (
<>
<button onClick={buy} disabled={status === "opening"}>
50 ⭐ ga sotib olish
</button>
<p role="status">{TEXT[status]}</p>
</>
);
}Konsolda:
invoice: https://t.me/$sinov-invoice
natija: paidEkranda:
50 ⭐ ga sotib olish
To'landi! Kitob tasdiqlanishi bilan ochiladi.E'tibor bering: "paid" bo'lsa ham kitob darhol ochilmaydi. Callback brauzerda ishlaydi — uni soxtalashtirish oson (DevTools'da callback("paid")). Kitob — server successful_payment ni olgandan keyin, serverdan keladi. O'tgan darsdagi "holat faqat serverdan" qoidasi.
6. CloudStorage: foydalanuvchi sozlamalari
6.1 Nima va qachon
WebApp.CloudStorage — Telegram serverlarida, shu bot va shu foydalanuvchi uchun kichik kalit-qiymat ombori. Afzalligi — mehmon telefondan ham, kompyuterdagi Telegram'dan ham bir xil sozlamani ko'radi (localStorage esa har qurilmada alohida). Cheklovlar (hujjat bo'yicha):
- 1024 tagacha kalit;
- kalit — 1–128 belgi, faqat
A-Z,a-z,0-9,_,-; - qiymat — satr, 0–4096 belgi.
Mos keladi: tanlangan til, sevimli taomlar, oxirgi manzil. Mos kelmaydi: buyurtmalar tarixi (bu — server bazasi), maxfiy narsa (bu — umuman brauzer emas).
6.2 Jonli: callback'ni Promise qilish
API callback uslubida: setItem(key, value, (error, ok) => …). React'da async/await qulay — o'ramiz (promisify):
import { useEffect, useState } from "react";
// O'quv modeli: CloudStorage (haqiqiysi — Telegram serverida)
const store = new Map();
const CloudStorage = {
setItem(key, value, cb) {
setTimeout(() => {
if (!/^[A-Za-z0-9_-]{1,128}$/.test(key)) {
return cb(new Error("KEY_INVALID"));
}
store.set(key, value);
cb(null, true);
}, 30);
},
getItem(key, cb) {
setTimeout(() => cb(null, store.get(key) ?? ""), 30);
},
};
// (xato, natija) callback'ini Promise'ga aylantiradi
const settle = (resolve, reject) => (e, v) =>
e ? reject(e) : resolve(v);
const cloud = {
get: (key) =>
new Promise((res, rej) =>
CloudStorage.getItem(key, settle(res, rej)),
),
set: (key, value) =>
new Promise((res, rej) =>
CloudStorage.setItem(key, value, settle(res, rej)),
),
};
export default function App() {
const [favorites, setFavorites] = useState([]);
useEffect(() => {
cloud
.get("sevimli")
.then((v) => setFavorites(v ? v.split(",") : []));
}, []);
async function add() {
const next = [...favorites, "manti"];
await cloud.set("sevimli", next.join(","));
setFavorites(next);
try {
await cloud.set("sevimli taomlar", "x"); // bo'sh joy — taqiq
} catch (e) {
console.log("xato:", e.message);
}
}
return (
<>
<p>Sevimlilar: {favorites.join(", ") || "yo'q"}</p>
<button onClick={add}>Sevimliga qo'shish</button>
</>
);
}Konsolda:
xato: KEY_INVALIDEkranda:
Sevimlilar: manti
Sevimliga qo'shishQiymat — faqat satr, shuning uchun ro'yxat join(",") bilan saqlandi (murakkabroq ma'lumot — JSON.stringify, lekin 4096 belgi chegarasini unutmang). Kalitda bo'sh joy — xato. Bizning mock xato matnini o'ylab topdi; haqiqiy SDK'ning xato matnini hujjat aniq bermaydi — xatoni ushlang, matniga tayanmang.
Bot API 9.0 dan yana ikki ombor bor: DeviceStorage (qurilmada, katta hajm) va SecureStorage (qurilmaning himoyalangan xotirasi — token kabi narsalar uchun, kam hajm).
7. Production: ekran, sinov, tunnel
7.1 To'liq ekran va xavfsiz zona
Bot API 8.0 dan Mini App butun ekranni egallashi mumkin — o'yinlar va media uchun. Lekin endi kontent telefonning "teshik"i va tizim paneli ostiga tushishi mumkin. SDK xavfsiz zonalarni beradi (mobil qurilma tuzoqlari):
if (WebApp.isVersionAtLeast("8.0")) {
WebApp.requestFullscreen();
WebApp.onEvent("fullscreenChanged", () => {
console.log("to'liq ekran:", WebApp.isFullscreen);
});
}.sarlavha {
/* telefon tizim paneli va Telegram tugmalari ostida qolmasin */
padding-top: calc(
var(--tg-safe-area-inset-top, 0px) +
var(--tg-content-safe-area-inset-top, 0px)
);
}safeAreaInset — tizim (kamera teshigi, holat paneli), contentSafeAreaInset — Telegram'ning o'z elementlari (yopish tugmasi). Eski Telegram'da metod yo'q — isVersionAtLeast shuning uchun.
7.2 Lokal sinash: HTTPS tunnel
Telegram Mini App manzilini faqat HTTPS bilan ochadi. Sizning Vite server'ingiz esa http://localhost:5173. Ikki yo'l:
1. Tunnel — lokal portni internetga HTTPS manzil bilan chiqaradigan dastur. Masalan, Cloudflare'ning cloudflared (hisobsiz "tez tunnel" rejimi bor) yoki ngrok (hisob kerak):
cloudflared tunnel --url http://localhost:5173
# → https://<tasodifiy-nom>.trycloudflare.comShu manzil BotFather'da menyu tugmasiga yoziladi. Vite esa begona host nomini to'sadi — dev server darsidagi xavfsizlik:
// vite.config.ts
export default defineConfig({
server: {
// tunnel manzili; faqat dev uchun
allowedHosts: [".trycloudflare.com"],
},
});Usiz brauzerda Vite'ning xabari chiqadi: "Blocked request. This host (…) is not allowed. To allow this host, add … to server.allowedHosts in vite.config.js." — xabarning o'zi davosini aytadi. Tunnel ochiq turganda lokal server'ingiz internetda — ishlab bo'lgach yoping.
2. Test serveri — Telegram'ning alohida sinov muhiti: alohida akkaunt, alohida bot (BotFather u yerda ham bor). Hujjatga ko'ra test muhitida Mini App manzili HTTP (TLS'siz) bo'lishi mumkin — lokal tarmoqdagi IP bilan sinash oson. Test akkauntiga kirish yo'li har ilovada boshqacha (iOS'da sozlamalar belgisini 10 marta bosish va hokazo — hujjatdagi "Testing Mini Apps").
Biz ikkalasini ham sinamadik — bot yo'q. Tunnel buyrug'i va Vite sozlamasi — hujjat bo'yicha.
7.3 Telegram Login Widget (oddiy sayt uchun)
Mini App'dan tashqarida, oddiy saytda "Telegram orqali kirish" kerak bo'lsa — Telegram Login Widget:
<script
async
src="https://telegram.org/js/telegram-widget.js?22"
data-telegram-login="sizning_bot"
data-size="large"
data-onauth="onTelegramAuth(user)"
data-request-access="write"
></script>Tugma bosilganda Telegram foydalanuvchidan ruxsat so'raydi va onTelegramAuth ga { id, first_name, username, photo_url, auth_date, hash } beradi. Bu ham imzolangan, lekin boshqa algoritm: maxfiy kalit — SHA256(bot_token) (HMAC emas), "WebAppData" yo'q. Ikki algoritmni adashtirish — keng tarqalgan xato. Saytning domeni BotFather'da /setdomain bilan bog'lanadi. CSP'da: script-src https://telegram.org, frame-src https://oauth.telegram.org.
Login Widget skripti <iframe> va o'z skriptlarini yuklaydi — vazifalar kabi Trusted Types yoqilgan sahifada CAPTCHA'lardagi kabi muammo bo'lishi mumkin (CAPTCHA darsida o'lchov). Biz Login Widget'ni sinamadik.
7.4 Chiqarishdan oldin
- BotFather: bot nomi, tavsifi, rasmi, menyu tugmasi, asosiy Mini App (
/newappyoki bot sozlamalari), maxfiylik siyosati havolasi. - Hajm va tezlik: WebView, mobil internet — birinchi yuklash kichik (optimallashtirish).
- CSP:
telegram.org(SDK skripti), to'lov bo'lsa — provayder hostlari. - Telegram'dan tashqarida ochilganda — tushunarli xabar, yiqilish emas.
7.5 Nega vazifalar da yo'q
Oldingi darsdagi sabab: bot = hisob va token, imzo tekshiruvi = backend. Kanon bu darsda ham o'lchov qilmadi: @twa-dev/sdk va @tma.js/sdk-react raqamlari — Mini Apps I da. Bu darsning jonli bloklari — o'quv modellari, initData imzosi esa Node'da haqiqiy algoritm bilan.
8. Hujumchi nigohi
1. Soxta initData. Hujumchi hash'ga o'z user ini yozadi. Himoya — server imzo tekshiruvi (validate). Imzosiz yoki noto'g'ri imzoli so'rov — 401.
2. Qayta o'ynatish (replay). Hujumchi kimningdir haqiqiy initData sini ushlab oldi (masalan, mehmon skrinshot yoki log yubordi). Imzo to'g'ri! Himoya — auth_date muddati va sessiyani faqat birinchi so'rovda ochish. initData ni loglarga yozmang — u vaqtinchalik "parol".
3. Bot tokeni sizib chiqishi. Token frontend'da, ochiq repo'da, .env commit qilingan — har kim imzo yasaydi va botingizni boshqaradi. Himoya: token faqat server env'ida; sizib chiqsa — BotFather'da darhol /revoke.
4. start_param orqali hujum. ?startapp=… — havolani hujumchi ham yasaydi. Uni yo'lga, so'rovga, HTML'ga tekshirmasdan qo'yish — ochiq yo'naltirish yoki XSS. Himoya — «Jonli: parametrdan yo'l» dagi oq ro'yxat.
5. Soxta "paid". openInvoice callback'i — brauzerda. Mahsulot faqat server successful_payment dan keyin beriladi.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Imzo hech qachon mos kelmaydi | maydonlar tartiblanmagan, \n o'rniga &, yoki URL-kodlangan qiymat ishlatilgan |
URLSearchParams (qiymatni o'zi ochadi), sort(), \n |
| Imzo mos emas (Login Widget kodi bilan) | Login Widget va Mini App algoritmi aralashgan | Mini App: HMAC("WebAppData", token) |
| Bir soatdan keyin ilova "buziladi" | har so'rovda auth_date tekshiriladi |
initData bilan sessiya ochish, keyin cookie |
| Muddat doim "eskirgan" | Date.now() (ms) va auth_date (s) |
Math.floor(Date.now() / 1000) |
startapp ishlamaydi |
/taom/42 — ruxsat etilmagan belgi |
taom-42 va o'z qoidangiz |
KEY_INVALID yoki saqlanmadi |
kalitda bo'sh joy/nuqta, qiymat 4096 dan uzun | A-Z a-z 0-9 _ -, qisqa qiymat |
Blocked request. This host … is not allowed |
Vite tunnel hostini bilmaydi | server.allowedHosts |
| Raqamli mahsulot uchun Payme ulandi, ilova bloklandi | Mini App'da raqamli savdo — Stars | Stars (XTR) |
10. Mashqlar
Mashqlar kurs/mashqlar/21/23-tma-auth/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Mini App maxfiy kalitini yasashda HMAC kaliti bo'ladigan satr:
- Eski
initDatani qayta ishlatishga qarshi tekshiriladigan maydon: - Mini App'ni kerakli sahifada ochadigan havola parametri:
- Telegram Stars valyuta kodi:
- Bot
pre_checkout_queryga javob berishi kerak bo'lgan muddat (soniya):
Yechim
HMAC_SHA256(bot_token, "WebAppData") — maxfiy kalit. auth_date — ochilgan vaqt (Unix soniya). startapp → start_param. XTR — Stars, provider_token bo'sh. 10 soniyada javob bo'lmasa — to'lov bekor.
2-mashq (o'rta): data-check-string
Imzo tekshiruvining birinchi qadamini alohida yozing: dataCheckString(initData) — hash ni olib tashlab, qolgan maydonlarni alifbo tartibida kalit=qiymat qilib \n bilan qo'shsin. Sinov:
"user=%7B%22id%22%3A1%7D&auth_date=1760000000&query_id=AAH&hash=abc"Natijada user qiymati ochilgan ({"id":1}) bo'lishi kerak, hash — yo'q. Keyin xuddi shu satrni & bilan ajratib, qo'lda (split("&")) qiling va farqni konsolga chiqaring.
Ishora: URLSearchParams qiymatlarni o'zi ochadi (%7B → {), split("=") esa — yo'q (URLSearchParams).
Yechim
function dataCheckString(initData) {
const params = new URLSearchParams(initData);
params.delete("hash");
return [...params]
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join("\n");
}
function wrongWay(initData) {
return initData
.split("&")
.filter((pair) => !pair.startsWith("hash="))
.sort()
.join("\n");
}
const sample =
"user=%7B%22id%22%3A1%7D&auth_date=1760000000" +
"&query_id=AAH&hash=abc";
console.log(dataCheckString(sample));
console.log("---");
console.log(wrongWay(sample));Konsolda:
auth_date=1760000000
query_id=AAH
user={"id":1}
---
auth_date=1760000000
query_id=AAH
user=%7B%22id%22%3A1%7DIkkinchi variantda user kodlangan holda qoldi — undan yasalgan HMAC Telegram'nikiga hech qachon teng bo'lmaydi va har bir haqiqiy foydalanuvchi rad etiladi. "Imzo hech qachon mos kelmaydi" xatosining eng ko'p sababi shu. localeCompare o'rniga oddiy sort() ham ishlaydi — kalitlar lotin harflarida.
3-mashq (qiyin): useTelegramSession — qayta urinish bilan
«Jonli: initData bilan kirish» dagi hook'ni kengaytiring. Soxta server birinchi so'rovda { ok: false, reason: "eskirgan" }, ikkinchisida { ok: true, user: { name: "Aziz" } } qaytarsin.
deniedholatida sababi ko'rsatilsin: «Kirish rad etildi: eskirgan» va «Qayta urinish» tugmasi.- Tugma so'rovni qayta yuborsin (effekt
attemptstate'iga bog'langan). - Har so'rov konsolga:
so'rov N.
Yechim
import { useEffect, useState } from "react";
const initData =
"user=%7B%22id%22%3A1%7D&auth_date=1760000000&hash=…";
const replies = [
{ ok: false, reason: "eskirgan" },
{ ok: true, user: { name: "Aziz" } },
];
let calls = 0;
function api() {
calls += 1;
console.log(`so'rov ${calls}`);
const reply = replies[calls - 1] ?? replies[1];
return new Promise((resolve) =>
setTimeout(() => resolve(reply), 150),
);
}
function useTelegramSession(attempt) {
const [state, setState] = useState({ status: "loading" });
useEffect(() => {
let active = true;
setState({ status: "loading" });
api("/api/sessiya", {
headers: { Authorization: `tma ${initData}` },
}).then((res) => {
if (!active) return;
setState(
res.ok
? { status: "ok", user: res.user }
: { status: "denied", reason: res.reason },
);
});
return () => {
active = false;
};
}, [attempt]);
return state;
}
export default function App() {
const [attempt, setAttempt] = useState(1);
const session = useTelegramSession(attempt);
if (session.status === "loading") return <p>Kirilmoqda…</p>;
if (session.status === "denied")
return (
<p>
Kirish rad etildi: {session.reason}{" "}
<button onClick={() => setAttempt((a) => a + 1)}>
Qayta urinish
</button>
</p>
);
return <p>Xush kelibsiz, {session.user.name}!</p>;
}Konsolda:
so'rov 1
so'rov 2Ekranda:
Xush kelibsiz, Aziz!active bayrog'i — race condition dan himoya: tez ikki marta «Qayta urinish» bosilsa, eski javob yangisini bosib ketmaydi. Haqiqiy Mini App'da "eskirgan" bo'lsa, qayta so'rov o'sha initData bilan yana rad etiladi — foydalanuvchiga "Ilovani yopib, qayta oching" deyish to'g'riroq (Telegram yangi initData beradi). Bu yerda soxta server ikkinchi marta "ok" deydi — oqimni ko'rsatish uchun.
4-mashq: tajriba qadami (ixtiyoriy)
vazifalar ga tegmang. 2-mashqdagi dataCheckString va darsdagi validate ni bitta Node faylga (kurs/tajriba/tma-imzo/tekshir.mjs) yig'ing va node:test bilan to'rt test yozing: to'g'ri imzo, o'zgartirilgan user, eski auth_date, hash siz satr (node:test). Token — soxta, darsdagidek. Agar o'zingiz bot yaratgan bo'lsangiz (ixtiyoriy) — Mini App'da WebApp.initData ni konsolga chiqarib, uni o'z tokeningiz bilan tekshirib ko'ring va natijani hech qayerga ulashmang.
Yechim
Test tuzilmasi (qisqacha):
import { test } from "node:test";
import assert from "node:assert/strict";
test("to'g'ri imzo", () => {
assert.equal(validate(initData, TOKEN, 3600, now), "to'g'ri: Aziz");
});
test("o'zgartirilgan user", () => {
const forged = initData.replace("Aziz", "Jasur");
assert.equal(validate(forged, TOKEN, 3600, now), "imzo noto'g'ri");
});
// … eskirgan, hash yo'qHaqiqiy initData ni tekshirganda signature maydoni ham keladi — u data-check-string'ga kiradi (faqat hash olib tashlanadi). Uni o'chirib yuborsangiz — imzo mos kelmaydi.
11. Real ishda
- Mini App backend'i — Telegram'dagi har jiddiy ilovada:
initDatatekshiruvi, sessiya, buyurtmalar. Ko'p jamoalarda frontend'chi ham shu tekshiruvni o'qiy olishi kutiladi — xatoni qayerdan izlashni bilish uchun. - Kanal va bot havolalari — marketing: har postda
startappbilan aniq taom yoki aksiya. Analitikadastart_param— "qaysi post olib keldi" (analitika). - Stars — kontent sotuvchilar, o'yinlar, premium funksiyalar. Jismoniy savdo — o'z checkout'ingiz yoki Telegram Payments provayderlari.
- Intervyu savollari: "Mini App'da foydalanuvchini qanday autentifikatsiya qilasiz?", "Nega
initDataUnsafega ishonib bo'lmaydi?", "Replay hujumidan qanday himoyalanasiz?", "Raqamli mahsulot uchun qaysi to'lov?", "Lokal Mini App'ni qanday sinaysiz?".
Xulosa
initData— imzolangan konvert: server data-check-string'danHMAC(HMAC("WebAppData", token), …)yasabhashbilan solishtiradi (timingSafeEqual) vaauth_datemuddatini tekshiradi; token — faqat serverda.- Frontend
initDataniAuthorization: tma …bilan yuboradi; server sessiya ochadi; ekrandagi foydalanuvchi — server javobidan. startapp→start_param: xavfsiz chegara — 64 belgi,A-Z a-z 0-9 _ -; yo'l — oq ro'yxatli qoidalar bilan, qolgani — bosh sahifa.- Raqamli savdo — Stars (
XTR): invoice serverda,openInvoicecallback'i — faqat xabar, mahsulot —successful_paymentdan keyin. CloudStorage— bot+foydalanuvchi uchun 1024 kalit, qiymat ≤ 4096; callback API'ni Promise'ga o'rash.- Production: to'liq ekran va
--tg-safe-area-inset-*,isVersionAtLeast, HTTPS tunnel +allowedHostsyoki test serveri; Login Widget — boshqa imzo algoritmi.
Keyingi dars: Tashqi login va SMS kod bilan kirish UI — «Google bilan kirish» tugmasi, OAuth qaytish sahifasi va PKCE'da frontend'ning roli, SMS kod ekrani va qayta yuborish taymeri.
Manbalar
- Telegram: "Telegram Mini Apps" — "Validating data received via the Mini App", "Validating data for Third-Party Use",
startapp("Direct Link Mini Apps"),openInvoice,CloudStorage,requestFullscreen,safeAreaInset, "Testing Mini Apps" — core.telegram.org/bots/webapps - Telegram: "Telegram Stars" va "Bot Payments API for Digital Goods" — core.telegram.org/bots/payments-stars; "Telegram Login Widget" — core.telegram.org/widgets/login
- Telegram Mini Apps platformasi (jamoa): "Init Data", "Authorizing User" (
Authorization: tma) — docs.telegram-mini-apps.com - Vite:
server.allowedHosts— vite.dev/config/server-options - Node.js:
crypto.createHmac,crypto.timingSafeEqual— nodejs.org/api/crypto.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!