前置工作
首先你要有小飞机账号 然后进入tg 机器人管理设置一个登录机器人
在聊天框输入/newbot 创建一个登录机器人

此时它会让你设置一个机器人名字

接着它会让你输入机器人的 username,注意 username 必须是以 bot 结尾的,比如 zaq_login_bot

它会给你返回一串 token 用于校验数据,图片里我打的马赛克地方即它返回的 token
token 的格式为:数字+:+一串字符串,我们需要注意的是这里的数字是它对应登录机器人程序的 id,后面也许会用到。
而 token 的作用是用于服务器校验 tg 授权登录成功后返回的数据是否正确。
还剩最后一个步骤,那就是设置一个域名白名单,这个白名单用于 tg 授权登录的地址。简单来说就是你在 A 域名下配置的 tg 登录,并且在 tg 登录里设置了 A 域名,那么此时可以正常授权登录。如果你在 B 域名里使用 tg 授权登录,此时会无法授权成功,会提示“Bot Domain invalid”。这是因为 tg 登录机器人配置的域名白名单是 A 域名的,在 B 域名正常是无法白名单使用 A 域名的 tg 登录,需要用到其他方法后面会讲。
设置白名单 在聊天输入框里输入/setdomain

此时让你选择一个登录机器人(如果有多个登录机器人的话)来设置对应的域名白名单,你选择要配置的登录机器人

在聊天框输入白名单域名即可,输入成功会提示“Success! Domain updated. /help”
补充几点:
- 配置的白名单是即时的,你输入了一个域名会即时生效。
- 一个机器人只能设置一个对应的白名单域名,无法让一个登录机器人对应多个域名。
- 需要更新域名只需要重新设置一遍即可
- 删除域名输入/empty
- 有用的数据为 username(你输入的以 bot 结尾的名字)和 token (token 里还有 bot id 可能会用的到)
前置工作已经完成了,接下来是登录授权流程。
授权登录有两个方式,先说 tg 推荐的方式。
Widget 组件授权登录
使用 tg 的 widget 登录组件来登录,优点是没啥太多要配置的东西,傻瓜式点击就会弹出登录窗口。
缺点就是由于是傻瓜式的方式,所以灵活性不够,不能自定义。

这里红框内的即是它的样式效果,只需要复制对应的 embed code 文本框的代码就可自动渲染。 当然我们使用现代化的框架肯定不会让它这么直接执行,这里以 react 为例
在 useEffect 里执行 tg login widget 代码
useEffect(() => {
// 动态添加 Telegram 登录小部件的脚本
const script = document.createElement("script");
script.src = "https://telegram.org/js/telegram-widget.js?7";
script.async = true;
script.onload = () => {
// 初始化 Telegram 登录小部件
// @ts-ignore
window.TelegramLoginWidget = window.TelegramLoginWidget || {};
// @ts-ignore
window.TelegramLogin = onAuth; // 设置全局回调函数
};
script.onerror = () => {
// 脚本加载失败
$toast("load script fail!");
};
// 将 script 标签添加到 DOM 中
document.body.appendChild(script);
// 清理操作
return () => {
document.body.removeChild(script);
};
}, []);
这里说明一下 onAuth 是接收授权回调的信息,但是没啥用,因为授权成功它会自动重定向到配置的地址里,导致 onAuth 没有来得及去执行,这里可以注释掉。
return (
<Wrapper>
<img src='/images/brazil/sign/telegram.png' alt='Telegram' className='tg-icon' />
{/* Telegram 登录widget */}
<div
className='auth-btn'
onClick={() => {
console.log(`click event in tg login`)
}}
>
<script
async
src='https://telegram.org/js/telegram-widget.js?7'
data-telegram-login={bot}
data-size='large'
data-radius='10'
data-request-access='write'
data-auth-url={`/login?provider=TELEGRAM&arguments=${redirectUrl}`}
/>
</div>
</Wrapper>
)
...
const Wrapper = styled.div`
position: relative;
overflow: hidden;
width: ${rem(36)};
height: ${rem(36)};
.tg-icon {
width: 100%;
height: auto;
pointer-events: none;
}
.auth-btn {
position: absolute;
top: 0;
left: 0;
opacity: 0;
z-index: 5;
#telegram-login-zaq_login_bot {
width: ${rem(36)};
height: ${rem(36)};
}
}
`
这里我是通过取巧的方式来实现自定义效果,即让 tg 登录按钮不显示,传入一张 icon 图片并让它点击穿透,这样当你点击的时候就是直接点击到 tg 登录按钮上了。 data-telegram-login 是你登录机器人的 username,即上面配置的 bot 结尾的那个 username. data-auth-url 是授权成功后 tg 弹窗会自动关闭弹窗页面并让页面重定向的地址(所以我才说上面的 onAuth 无效,因为页面跳转了会丢失数据。
授权成功之后 tg 会把用户信息携带到 data-auth-url 上,此时你只需要把地址栏里的信息传递给服务器校验就大功告成了。
通过这种方式我们即实现了自定义又能快速授权登录,需要有个注意的点是我们的自定义效果是图片样式pointer-events: none; 它会在 h5 首次点击无反应,应该是浏览器自行处理的效果,而第二次点击就正常了。
授权链接登录
另一种方式是通过授权链接的方式来实现的登录,这种方式就灵活度+++了。它是通过链接打开授权弹窗页面,这样我们就可以自定义点击方式和控制流程了。
https://oauth.telegram.org/auth?bot_id=<YOUR_BOT_ID>&embed=0&origin=<CALLBACK_URL>
botid 即是我上面说的 token 字符串里最前面的数字
origin 同样是授权成功后的重定向地址
此时我们可以通过 click 点击事件,window.open 打开链接,此时授权成功之后同样会携带授权参数到重定向链接里,我们同样拿地址栏里的参数给服务器校验
上面两种方式都能实现 tg 授权登录。
但是由于授权成功自动重定向不能避免,所以它们会重新加载页面,而我们现在基本都是 spa 单页面应用,怎么看都不符合影响用户体验吧。而且授权登录白名单只能配置一个,那意味着我们有多个域名网站,岂不是每个域名都要对应一个登录机器人?那 100 个网站就需要 100 个登录机器人?
所以接下来我们探索如何实现一个域名来登录多个网站
刚开始我使用的是 widget 登录组件+自定义的方式来登录的,我的想法是实现一个中转页,在中转页点击授权登录,授权成功再通过 postMessage 将数据传递给登录页,所以我的想法是通过 iframe 来加载中间页,再通过图片点击穿透直接点击到 iframe 里来达成授权登录。 结果是 tg 限制了 widget 组件登录 iframe 只能为当前白名单域名才可以,你 iframe 加载中转页(白名单页)tg 会阻止你授权登录。 widget 另一种是 window.open 打开中转页再手动点击授权登录,那此时的流程为 window.open 打开中转页,还需要在中转页点击授权登录按钮,体验更差了,所以 widget 组件登录我直接 pass 了。
接下来研究另一种方式,授权登录链接。 因为怕授权登录页会像 widget 组件一样通过 Iframe 加载会被 tg 登录阻止,所以一开始我是通过 window.open 打开授权链接,此时页面会自动打开授权弹窗。所以会连续打开两个弹窗,成功之后再依次关闭弹窗。这样的用户体验也似乎不太好,上面的领导也不接受。所以在此修改流程,通过 iframe 加载中转页,通过 postMessage 与组件进行通信,告知 iframe 何时调用授权弹窗。此时只会打开一个授权弹窗,达到了 google 登录一样的效果就大功告成了。
注意:中转页是需要部署上去的,用于各个网站授权登录,所以必须能访问的到!
附中转页逻辑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auth Telegram Login</title>
</head>
<body>
<script>
const params = new URLSearchParams(window.location.search);
const origin = decodeURIComponent(params.get('origin'))
const originUrl = params.get('origin')
function openAuth(bot_id, origin) {
window.open(`https://oauth.telegram.org/auth?bot_id=${bot_id}&embed=0&origin=${origin}`, `TelegramLoginBot_${bot_id}`, 'width=600,height=500,toolbar=no,status=0,location=0,menubar=0,toolbar=0,top=0,left=0')
}
window.addEventListener('message', event => {
if (event.origin === 'https://oauth.telegram.org') {
window.parent.postMessage(
{ type: 'AUTH_SUCCESS', data: event.data },
decodeURIComponent(originUrl)
);
window.close()
} else if (originUrl && event.origin === decodeURIComponent(originUrl)) {
// 打开授权登录弹窗
switch(event.data.type) {
case 'AUTH_LOGIN':
event.data.data.botId && openAuth(event.data.data.botId, event.data.data.origin)
break;
}
}
})
</script>
</body>
</html>
登录页
function onAuth() {
iframe.current?.contentWindow?.postMessage(
{
type: 'AUTH_LOGIN',
data: {
botId: <bot id>,
origin: '<部署的中转页地址域名>',
},
},
'<部署的中转页地址域名>',
)
}
useEffect(() => {
window.addEventListener('message', (ev) => {
console.log(ev.data)
if (ev.origin === '<部署的中转页地址域名>') {
const { type, data } = ev.data
switch (type) {
case 'AUTH_SUCCESS':
getResult(data)
window.focus()
break
}
}
})
}, [])
<iframe
src={`<部署的中转页地址域名>?origin=${encodeURIComponent(
window.location.origin,
)}`}
id='tg_auth_login'
title='Telegram Auth Login'
ref={iframe}
/>
通过调用onAuth与 iframe 通信告知当前需要调用openAuth打开授权登录弹窗,授权成功之后通过监听 message 监听接收授权信息并通过 postMessage 发送给登录页,登录页接收到消息之后在执行对应的处理(提交服务器校验)
参考文档 掘金