本篇要解決的問題
之前在寫〈在網頁上嵌入Telegram 頻道廣播訊息〉這篇時,最後就有說看到 Telegram Widget 上有四種功能,其中就有一個「Telegram Login」。當時就記在筆記本上,打算有空檔時來玩玩看,如今在年假前終於有個空檔啦~
用 Telegram 登入功能並不難,畢竟官方就有提供程式碼生成器。比較耗時間是如果想要做後端資料正確性的驗證,就要有個解密的過程,而本篇用的是 Google Apps Script,不像 Node.js 上可以直接用 npm 的 package,花了一點時間在看 GAS 上要怎麼相對應的解密。
如果不需要驗證資料正確性,是可以很快就做出 Telegram 登入功能的。
本篇主要參考的文件是官方說明:Telegram Login Widget
Telegram 登入過程
先紀錄一下用 Telegram 登入會有什麼樣子的流程,以及能夠收到哪些資料。
如果想要自己試著用 Telegram 登入一下,可以進到官方頁面,頁面中就有個 SampleBot 讓人測試登入:Telegram Login for Websites
Telegram 登入按鈕長這樣子:
能改的是圓角的角度及按鈕的大、中、小,上面的截圖是「大」。
點擊登入按鈕後,會要求要輸入手機號碼:
輸入完手機,接著會跳一段訊息,意思是要我們開啟 Telegram,在 Telegram 上做確認登入:
手機上會看到這段:
按下「Confirm」就會登入,按下「Decline」就會拒絕。
我們按下「Confirm」後,接著訊息就會改變:
這段訊息就是紀錄我們的帳號在哪裡做了登入。按下「Terminate session」的話就會停止在這個站上的登入。
網頁上原本的小視窗會改為允許取得資訊的確認:
按下 ACCEPT 後,我們原本站上的登入按鈕會有所改變:
左邊的原按鈕會顯示名稱,右邊會顯示大頭照。
按照官方文件,使用者登入後,我們總共可以拿到的資料有 7 個:
- id
- first_name
- last_name
- username
- photo_url
- auth_date
- hash
可以看到的是,雖然是讓使用者輸入手機來登入,但我們是拿不到手機號碼的,除了 id、hash 這兩項,其他的都是公開資料。
以上就是 Telegram 的登入流程,可以看到最重要的就是那個登入按鈕。
Telegram 登入按鈕生成器
因為在做事情的大部份就是那顆登入按鈕,官方很貼心的也提供了登入按鈕的生成器:Widget configuration
生成器第一個需要的是「Bot Username」,因此我們需要建立一個 Telegram Bot。
後面的選項就是改按鈕大小、圓角角度、授權的回應方式、是否要讓機器人可以傳送訊息,最後一個就是產生出來的程式碼。
建立 Telegram 機器人
要建立 Telegram Bot,就跟官方機器人申請就好,先點擊以下連結加入官方機器人為好友:BotFather
接著開啟機器人的對話框,先輸入「/
」,就會出現選單,選擇第一個「/newbot
」,機器人就會一步一步用訊息帶著我們建立一個機器人:
完整的建立機器人圖文說明都寫在〈Telegram Bot學習筆記-1:用GCP + node.js接收/推播訊息〉,本篇不再重寫。
我們建立機器人時,就會填寫到 Bot Username,第一個 Bot Username 就是填在這,假如忘記的話,我們點進我們的機器人資訊中就可以看到:
設定白名單網域
建立完機器人,需要設定白名單的網域,一個機器人一次只能允許一個白名單,比方我們在白名單上設:
letswritetw.tw
那就只有在這個網域下才能夠進行登入,不在這網域下的,原本是登入按鈕的地方一律會變成「Bot domain invalid」的訊息。
設定白名單的方式很簡單,一樣是向 BotFather 設定。
在 BotFather 輸入「/setdomain
」並送出,我們的選單就會變成擁有的機器人清單:
選擇我們用來處理登入的機器人後,會出現一段訊息:
接著我們輸入我們要的 domain 並送出就可以了:
Authorization Type
授權的方式有二種:Callback、Redirect ot URL。
Callback
使用者在頁面上進行登入後,預設會直接呼 onTelegramAuth
這個 function,並把使用者的資料帶進去,範例的程式碼如下:
從範例的程式碼中可以看到,預設的 callback function 是 onTelegramAuth
,我們也可以修改 data-onauth
這裡的值去觸發我們想要的 function。
Redirect to URL
這一段就像是當使用者登入後,Telegram 會主動去呼我們的 API,method 是「GET
」,網址的參數上就會是使用者的資訊。
本篇在後面段落的示範是寫在 Google Apps Script 上,當 GAS 部署完後,連結會像是:
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxx/exec
我們在生成器上 Authorization Type 選擇 Redirect to URL後,再把這段網址貼上去就可以了:
Google Apps Script 的基本使用都寫在〈Google Apps Script 基本使用:跨網域AJAX、接Firebase〉這篇,本篇就不從頭解說。
在 GAS 上,我們只需要寫入以下程式碼,就可以取得使用者登入後,機器人拋來的使用者資料:
function doGet(e) { var param = e.parameter; // param.id 使用者 id // param.first_name 使用者「名」 // param.last_name 使用者「姓」 // param.photo_url 使用者大頭照路徑 }
hash 解密驗證
不論是用 Callback 還是 Redirect to URL,Telegram 都會提供一個「hash」出來,這個主要是給我們驗證這次來的資料是不是正確的。
在比對 hash 之前,我們需要二個東西,一個是我們登入機器人的 API Token,這個在建立時 BotFather 就會提供。
另一個是「data_check_string」,這個是要把所有收來的資料,不含「hash」,按照字母排序並用「\n
」做串接後,拼出來的。
假設我們取得的資料是這樣:
auth_date: 1611219924
id: 12345678
first_name: hello
last_name: world
hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
photo_url: https://t.me/xxxxxxxx
我們就把 hash 去掉,接著 key 值照字母排序,變這樣:
auth_date: 1611219924
first_name: hello
id: 12345678
last_name: world
photo_url: https://t.me/xxxxxxxx
接著我們把這些值的 key, value 用「=
」連接,各個值之間用「\n
」連接,需要的 data_check_string 就會是這樣:
auth_date=1611219924\nfirst_name=hello\nid=12345678\nlast_name=world\nphoto_url=https://t.me/xxxxxxxx
我們有了 API Token、data_check_string 這兩個值以後,接著開始做一連串的加解密,出來的值如果等於「hash」,就代表這次收到的資料確實是由 Telegram 提供的正確資料。
官方提供的程式碼如下:
data_check_string = ... secret_key = SHA256(<bot_token>) if (hex(HMAC_SHA256(data_check_string, secret_key)) == hash) { // data is from Telegram }
也有提供 PHP 版本的完整程式碼:check_authorization.php
本篇用的是 Goolge Apps Script,因此這邊附上 August 研究完後,用 Goolge Apps Script 上寫 Telegram Hash 加解密驗證的程式碼。
這段是血與淚的結晶,對 SHA256 什麼的不熟,更不用說還要在 GAS 上執行,真的是痛苦了幾個小時,不斷嘗試下最後才完成的。如果各位朋友在工作上有用到這段,或是這段對你有幫助,麻煩不要吝嗇的請 August 喝杯咖啡,支持一下本站繼續有文章產出:
放置登入按鈕、Demo
生成器的欄位都填好以後,最後就會出現一段要放的程式碼。
選 Callback 的程式碼會像以下:
只需要把第一個 script
放到我們想要顯示登入鈕的地方即可。
選 Redirect to URL 的程式碼會像以下:
就把這一整段放到我們想要顯示登入鈕的地方即可。
這邊 August 也用了原本就有開發的 Let’s Write – 生活小幫手 加上了登入功能,Demo 頁面在這邊:
https://letswritetw.github.io/letswrite-telegram-login/
提醒一下,從 Demo 頁登入後,會把機器人回傳的資料存在 Firebase 上喔!
可以設置ip為白名單嗎,我用ip設置白名單報錯了
Refused to frame ‘https://oauth.telegram.org/’ because an ancestor violates the following Content Security Policy directive: “frame-ancestors ‘我的ip’