创建 Bot 并保存 Token
在 SafeW 中通过 BotFather 创建 Bot。Token 只保存于服务端,用于之后验证授权数据。
Login Widget 在你的网站中显示 SafeW 登录按钮。用户授权后,你会收到带签名的基础身份数据,再由服务端验证并建立自己的登录会话。
复杂的签名验证留在服务端,前端只负责展示按钮、接收结果,并把数据安全地交给自己的后端。
进入 SafeW 授权确认
身份字段 + auth_date + hash
验签通过后才允许登录
Login Widget 依赖 Bot 身份。域名绑定确保授权结果只返回给你控制的网站。
在 SafeW 中通过 BotFather 创建 Bot。Token 只保存于服务端,用于之后验证授权数据。
绑定实际部署 Widget 的域名。建议让 Bot 头像与网站 Logo 保持一致,帮助用户识别授权对象。
两者都能完成授权,区别在于授权结果如何回到你的应用。data-onauth 与 data-auth-url 必须二选一。
授权后直接调用页面中的 JavaScript 函数,不离开当前页面。
授权后跳转到指定 URL,用户字段通过查询参数传递。
data-onauth 和 data-auth-url 二选一;若两者都不设置,按钮会显示,但授权结果不会被你的应用处理。
将 YOUR_BOT_USERNAME 替换为 Bot 用户名,不包含 @。Callback 模式中,回调函数收到 user 对象。
<script async
src="https://oauth.safew.bot/js/safew-widget.js"
data-safew-login="YOUR_BOT_USERNAME"
data-size="large"
data-onauth="onSafeWAuth(user)"
data-request-access="write">
</script>
<script>
function onSafeWAuth(user) {
fetch("/auth/safew", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user)
});
}
</script>
<script async
src="https://oauth.safew.bot/js/safew-widget.js"
data-safew-login="YOUR_BOT_USERNAME"
data-size="large"
data-auth-url="https://your-site.com/auth/safew"
data-request-access="write">
</script>
large、medium、small,默认使用 large。
data-userpic 控制是否显示,默认开启。
data-request-access="write" 可请求向用户发送消息的权限。
字段用于识别用户和验证授权。last_name、username 与 photo_url 可能不存在,业务逻辑应允许这些字段为空。
| 字段 | 类型 | 用途 |
|---|---|---|
id |
Integer | SafeW 用户唯一 ID |
first_name |
String | 用户名字 |
last_name |
String · 可选 | 用户姓氏 |
username |
String · 可选 | SafeW 用户名 |
photo_url |
String · 可选 | 头像地址 |
auth_date |
Integer | 授权 Unix 时间戳 |
hash |
String | 服务端校验哈希 |
客户端可以被篡改。必须把授权数据发送到服务端,以 Bot Token 派生密钥并验证 hash。
const crypto = require("crypto");
function verifySafeWLogin(payload, botToken) {
const data = { ...payload };
const receivedHash = data.hash;
delete data.hash;
if (!/^[a-f0-9]{64}$/i.test(receivedHash || "")) {
return false;
}
const checkString = Object.keys(data)
.sort()
.map((key) => `${key}=${data[key]}`)
.join("\n");
const secretKey = crypto
.createHash("sha256")
.update(botToken)
.digest();
const computedHash = crypto
.createHmac("sha256", secretKey)
.update(checkString)
.digest("hex");
const hashIsValid = crypto.timingSafeEqual(
Buffer.from(computedHash, "hex"),
Buffer.from(receivedHash, "hex")
);
const age = Math.floor(Date.now() / 1000) - Number(data.auth_date);
return hashIsValid && age >= 0 && age <= 3600;
}
始终在服务端验证 hash;拒绝过期 auth_date;使用 timingSafeEqual;Bot Token 永不进入前端;登录回调全程使用 HTTPS。
Login Widget 使用 SHA256(bot_token) 作为 HMAC 密钥;Mini App 使用 HMAC-SHA256("WebAppData", bot_token) 派生密钥。