mcpField
T
telegram-miniapps

telegram-miniapps

github.com/ssheleg开发4153 次安装4 天前更新
代码仓库

关于 telegram-miniapps

telegram-miniapps 是指导 Telegram Mini App 开发与安全验证的技能,面向实现 WebView 应用的开发者。它重点说明如何验证签名 initData、处理设备视口与安全存储,以及将结果返回机器人。

主要功能

  1. 01验证 HMAC 与 Ed25519 签名
  2. 02检查 initData 新鲜度与重复字段
  3. 03处理视口、安全区与主题
  4. 04说明应用向机器人传递结果
  5. 05提供可运行的 Python 验证器

适用场景

  1. 01实现 Telegram Mini App 登录验证
  2. 02审查 initData 校验与会话交换
  3. 03修复手机 WebView 布局问题
  4. 04将 Mini App 操作结果发送给机器人

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ssheleg/telegram-dev --skill telegram-miniapps

安装后在 Claude Code 中输入/telegram-miniapps确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

fixtures/
verify_initdata.py
references/
app-to-bot.md
initdata-verification.md
viewport-and-platform.md

详细介绍

Telegram Mini Apps — 一个经过签名的数据块就是全部依据

Mini App 是一个附带查询字符串的 WebView 网页。**这个查询字符串是证明用户身份的唯一依据,并且由你的 bot 令牌签名。**在服务器上验证它,你的身份验证强度就与 bot 本身相当;信任 SDK 交给你的解析后对象,则毫无保障。

对照 core.telegram.org/bots/webapps 阅读,日期为 2026-08-25,Bot API 10.3。

深入内容,按需加载:

阅读适用场景
references/initdata-verification.md实现或审查验证逻辑 — 两种算法,三种语言,以及失败场景
references/app-to-bot.md向 bot 返回结果、内联查询,以及在应用内付款
references/viewport-and-platform.md应用在真机上显示异常 — 视口、安全区域、全屏、主题

可运行,并随本文件一同提供: fixtures/verify_initdata.py — 可直接复制的验证器, 以及九项检查,确保它会拒绝遭篡改的用户 ID、过期的 auth_date、重新序列化的 user 字段和顺序颠倒的密钥派生。python3 fixtures/verify_initdata.py 只需标准库即可运行。


initDataUnsafe 这个名字是有意为之

// The client hands you both. One is evidence, the other is a convenience.
const raw    = window.Telegram.WebApp.initData;        // the signed string — SEND THIS
const unsafe = window.Telegram.WebApp.initDataUnsafe;  // already parsed — display only

initDataUnsafe.user.id 是一个用户可以随意设置的数字,因为它来自运行在用户设备上的网页。**关于你是谁、你拥有什么、你能看到什么,所有这些决定都必须在服务器上根据原始字符串重新作出。**将 initData 发送到你的后端,在那里验证它,并根据验证结果确定身份。

这项技能旨在防止的唯一缺陷:接受客户端传入 { userId } 的端点。它能正常工作,也能用于演示,但会让任何人都能读取任何人的数据。

验证,务必做到准确

import hashlib, hmac, time
from urllib.parse import parse_qsl

MAX_AGE = 300  # seconds; a decision, not a constant handed down

def verify(init_data: str, bot_token: str) -> dict:
    pairs = dict(parse_qsl(init_data, strict_parsing=True))   # already url-decoded
    received = pairs.pop("hash", None)
    if not received:
        raise ValueError("no hash")
    pairs.pop("signature", None)   # the OTHER protocol's input — see below

    check = "\n".join(f"{k}={pairs[k]}" for k in sorted(pairs))
    secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    expected = hmac.new(secret, check.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, received):
        raise ValueError("bad signature")
    if time.time() - int(pairs["auth_date"]) > MAX_AGE:
        raise ValueError("stale")
    return pairs

以下五种情况都会导致验证失败,但仍会让某些人的代码返回“有效”:

  • 网上一半的代码片段都把密钥派生方向写反了。 密钥是 HMAC_SHA256(key="WebAppData", msg=bot_token)——常量是密钥,令牌是消息。交换两者会得到一个永远无法匹配的稳定哈希,而常见的“修复方法”就是不再检查。
  • 按键排序,用 \n 连接,并使用收到的原始值——只进行一次 URL 解码,不重新编码,也不重新序列化。user 是 JSON 字符串,必须保持收到时的原样;解析后再重新转储会改变键的顺序并破坏哈希。
  • 在构造字符串前移除 hash,signature 也要移除——但要出于正确的原因。 signature 不是垃圾数据:它是 Ed25519 路径的输入,属于一个独立协议,采用独立的规范化方式(以 {bot_id}:WebAppData 为前缀,然后使用不含 hash 或 signature 的排序键值对)。应将两种规范化逻辑拆分成两个函数——fixtures/verify_initdata.py 中的 hmac_check_string 和 ed25519_check_string——因为一个供两者共用的辅助函数可能会让规范化错误通过自身验证;而 fixture 中的黄金测试向量(手动计算,绝不由待测逻辑生成)正是用来捕获此类错误的:带有 signature 的 initData 只有通过正确路径才能验证成功。
  • 在进行任何计算前,拒绝重复字段和无效数据。 parse_qsl 返回的是列表;将其折叠成字典会悄悄保留最后一个重复项,而 user=innocent&user=admin 会引发关于 HMAC 覆盖了哪个副本的争议,验证器不该遇到这种情况。应直接拒绝重复字段和无法解析的数据,并给 auth_date 设置一个未来时间上限(时钟偏差,以秒计),因为未来的时间戳只会变得更新。
  • auth_date 不能省略。 如果不设有效期窗口,被截获的 initData 就会成为永久的持有者令牌。选定一个窗口并明确说明,超出窗口的内容应视为未通过身份验证,而不是记下错误后继续处理。
  • 使用恒定时间比较。 对十六进制摘要使用 == 会形成计时预言机;语言提供的 compare_digest 等效函数几乎没有额外开销。

不要在每次请求时都验证

initData 用于登录,不是会话。验证一次,签发自己的短期令牌,其余 API 请求都使用该令牌。

POST /auth/telegram   { initData }   →  verify → your session JWT (short TTL)
GET  /api/...         Authorization: Bearer <your token>

每次调用都重新验证,会迫使你把 MAX_AGE 设得足以覆盖较长的会话,这和完全不检查 auth_date 没有区别。

没有 bot 令牌时的第三方验证路径

外部服务无法持有 bot 令牌,因此 Telegram 会用第二种方式对 initData 签名:使用可通过 Telegram 公钥验证的 Ed25519 signature。签名字符串有所不同——前缀为 <bot_id>:WebAppData,后跟换行符,再接上移除 hash 和 signature 后相同的排序 key=value 键值对。测试环境和生产环境使用的公钥不同。

当验证方不是 bot 的所有者时使用此方式。如果你确实持有令牌,不要用它替代 HMAC——HMAC 路径更简单,需要更换的密钥也更少。两种方式及其代码见: references/initdata-verification.md。

SDK 的包名已变更,但组织主页仍显示旧名称

  • 当前使用的包:@telegram-apps/sdk(截至 2026-08-25 为 3.11.8),旁边还有 @telegram-apps/sdk-react。
  • Telegram-Mini-Apps GitHub 组织仍主推 tma.js 和 @tma.js/sdk,最新版本为 3.3.0——这是较早的名称,落后了八个次版本。
  • 该组织中的模板(reactjs-template、nextjs-template、vuejs-template、solidjs-template)值得一读;那里还有四个模板已归档并标记为不受支持,Agent 复制搜索结果时,有约一半概率会选中其中一个。

这两个库都不是必需的。Mini App 接口是 window.Telegram.WebApp,由 telegram-web-app.js 提供;SDK 带来的是类型化访问和平台适配层,而非额外能力。

设备不是你的笔记本电脑

Mini App 会在 WebView 中渲染,键盘、标题栏和主页指示条都会占据屏幕空间。请读取视口字段,而不是想当然: viewportHeight、viewportStableHeight、safeAreaInset、 contentSafeAreaInset、isFullscreen、isActive。在两个平台上,100vh 都不正确,而且错法各不相同。

主题来自 themeParams 和 colorScheme,会随着用户设置变化,而不是由你控制——应绑定这些值,而不是硬编码调色板。 references/viewport-and-platform.md。

宿主位于你的页面之上,设备也是如此

以下论文和文档的阅读日期为 2026-10-19。

威胁模型:Telegram 客户端是可信的,因为页面里的任何东西都无法拒绝它。 Priyanka 等人发表于 Don't Trust the Super-App: A Case Study of Russia's Max(arXiv:2609.11814,2026-09-10)的研究以 MAX 超级应用为例,指出宿主可以捕获 Mini App 的 UI、读取和写入其本地存储、向其运行时注入任意 JavaScript、介入其网络流量,并以可实现静默冒充的方式控制其身份验证上下文;作者认为,这些能力源于超级应用在设计上天然拥有的权限。该研究针对的是 MAX,而不是 Telegram;请将其视为这类边界的示例。签名后的 initData 只能证明客户端为其内容背书,不能证明客户端是诚实的。因此,请将这一假设写入威胁模型,把秘密和决策留在服务端,并确保页面持有的数据有效期较短。

设备上的秘密应存入 SecureStorage,绝不要放入可读取的存储中。 Ciccotelli、Zappone 和 Di Pietro 在 TENET: Telegram Mini App (in)security(arXiv:2608.17538)中分析了 37 个 Mini App:其中 30 个存在三类漏洞(明文存储、可恢复的加密、可重放的令牌),存储内容包括会话令牌和钱包助记词。自 Bot API 9.0 起,Telegram.WebApp.SecureStorage 使用 iOS Keychain 和 Android Keystore,静态加密,每位用户每个 bot 最多可存储 10 项;DeviceStorage 类似于 localStorage(每位用户每个 bot 为 5 MB),不适合存储秘密。短期会话令牌应存入前者,助记词则两者都不应存。

TON Connect 5.0 只是重新编号,但从 3.0 升级的变化可不止于此。 2026-10-11,@tonconnect/ui 和 @tonconnect/ui-react 升至 5.0.0,以便与 @tonconnect/sdk 共用一个主版本号;其代码与 ui 3.1.0 相同(5.0.1 是视觉修复版)。3.1.0 改变了错误处理程序收到的内容:UI 错误类会在 error.name 中报告其类名(以前是 "Error"),而已知的钱包拒绝操作会以类型化 SDK 错误(例如 UserRejectsError)传递给应用,而不再是 TonConnectUIError: Unhandled error。Mini App 检测功能(isInTMA、getTmaPlatform 等)已移入 @tonconnect/sdk,移动端模态框也会将 Telegram 和 Gram Wallet 显示为独立按钮。从 3.0.x 升级前,请先针对类型化错误进行匹配。

将结果返回给 bot

  • sendData(data) ——用于通过键盘按钮打开的应用。它会关闭应用,并向 bot 发送 web_app_data。bot 仍必须将其视为用户输入,因为它确实来自用户。
  • answerWebAppQuery ——用于通过内联按钮打开的应用,需使用已验证的 initData 中的 query_id。该标识的有效期很短;如果服务端先执行耗时操作,就会发现它已经过期。
  • 付款通过 bot 使用 Stars 完成,走的是 telegram-bots 所述的同一条 pre_checkout_query → successful_payment 流程。 用户在 Mini App 中点击付款;确认款项并不是 Mini App 的职责。

发布前检查

  1. 服务端从经过验证的 initData 中获取身份,而不是使用客户端提供的 ID(§ initDataUnsafe 这个名称是有原因的)。
  2. 根据明确说明的时间窗口检查 auth_date,并将 initData 换成你自己的会话(§ 准确验证方式、§ 不要在每次请求时都验证)。
  3. 哈希比较采用恒定时间算法,并在构建校验字符串前移除 hash 和 signature。
  4. SDK 是 @telegram-apps/*,从组织复制的任何模板都不是已归档模板(§ SDK 的包名已更改)。
  5. 布局读取安全区域和视口字段,而不是使用 100vh(§ 设备不是你的笔记本电脑)。
  6. 威胁模型明确将宿主列为可信方,页面保存的任何机密都存放在 SecureStorage 中,而不是 localStorage 或 DeviceStorage(§ 宿主位于你的页面之上)。

触发词

更多会将你引导至此处的短语:"miniapp"、"WebAppData"、"@telegram-apps/sdk"。

从 0.5.4 版本的描述移至此处:"telegram web app"、"телеграм веб-апп"、"валидация initData"。

评论

支持 Markdown