在 Chrome ext/webapp 中實現 OAuth

這陣子剛好有機會接觸一些 OAuth 的操作,所以把心得記錄一下。

什麼是 OAuth?

OAuth
簡單地說,它是一種讓使用者在不同網站間,授權網站間資料交換的機制。舉例來說,當一個使用者在 X 網站做了一些動作,而希望把這些動態發佈到自己的 twitter 上,在這個情境之下,X 網站無法直接代替你去發送一則 tweet,但在透過 OAuth 的授權機制後,X 網站得到該使用者的許可,從 twitter 網站取得了一把萬能的鑰匙,從此之後便可以代替使用者去 twitter 發送 tweets 了,而且使用者隨時可以回到 twitter 上取消這個授權,確保當 X 網站做壞事時可以立刻止血。

由於 OAuth 已經是一個廣為大家接受的標準,所以現在許多網站的認證授權機制都使用 OAuth 的機制來作授權(Authorization)的動作。它與 OpenID 只作認證(Authentication)的概念完全不一樣。有興趣深入研究的朋友可以自行深入閱讀它們的規格書。

OAuth 機制的流程

這裡以 twitter 作例子,說明一下 OAuth 的步驟:

  1. 使用者 eric 希望讓網站 X 可以代替自己發送訊息到 eric 在 twitter 上的 Timeline,於是網站製作了一個連結或頁面,提示 eric 可以啟動 twitter 的 OAuth 流程
  2. 網站 X 透過 twitter 提供的 API 取得一組 request token(以及一個用來加密的 secret key 以及 request token 何時過期),接著網站 X 便利用 request token 組合出一個 twitter 的 URL,讓使用者到該 URL 進行授權的確認。
  3. 此時使用者 eric 被帶到 twitter 的頁面上,twitter 會先要求登入,再來就是告訴 eric 現在是要對哪個網站(或應用程式)進行授權,打算同意或是拒絕,像下圖那樣:
  4. 若使用者同意授權,此時 twitter 會把使用者帶回網站 X 事先設定好的 callback URL,同時將一個暫時的 verifier(以及 secret key)也一併(透過 HTTP GET)帶回網站 X,這時網站 X 再利用此 verifier 去完成最後的步驟,進而取得真正的 access token。之後網站 X 僅需要使用這個 access token 就能夠以 eric 的身份在他的 twitter 上發言了

在 Chrome extension/webapp 上的麻煩

只要你稍微想一下這個流程,就會發現這個機制對於 web 應用程式非常自然,但如果是在本地端獨立運行的應用程式呢?那不就沒有 callback URL 能夠讓待授權的網站回呼了嗎?同樣的問題也會發生在手機上的應用程式,而 Chrome extension/webapp 也算是在本地端運行的程式,這時就需要一些小技巧讓 OAuth 流程能夠順利完成所有的步驟,以下就以幾個知名的網站做例子,說明各種狀況(可能也是許多開發者剛好需要解決的狀況)該如何因應。

twitter OAuth

twitter 其實算是佛心來的(不過這好像是規格書裡提到應該要做的),它其實有為這種情況提供一種解決方案,在上述的 3 -> 4 步驟中,普通的狀況是網站要把使用者帶回 callback URL,但是 twitter 允許你在步驟 2 時,將 callback 參數設為 oob,這樣當使用者授權完之後,twitter 並不會把使用者帶回原網站,而是在畫面上顯示出一段 pin code:
twitter oauth pin code
這段 pin code 就可以拿來作為步驟 4 中的 verifier 來用,這時可以提供一個介面讓使用者將 pin code 輸入,再繼續取得 access token 的動作,像是這樣:

所以若是要在 Chrome ext/app 中完成 twitter OAuth 的話,在步驟 2 要帶使用者做授權時,可以新開一個 tab 把使用者帶去作授權:
chrome.tabs.create({
url: ['http://api.twitter.com/oauth/authorize?oauth_token=', oauthToken].join(''),
selected: true
});

而在開啟新 tab 的同時,也可以把要使用者輸入 pin code 的介面(如:對話盒)先準備好,就等使用者回來填寫 pin code 囉。

參考:Twitter API Authentication

Facebook 的授權

如果是要使用 Facebook 的 Graph API,那一樣要做一次 Facebook 的身份認證、授權流程,雖然 Facebook 是實作 OAuth 2.0 的規格,不過還是有一個關鍵的 callback URL 要處理,目前它官方只能設定 http://https:// 開頭的 callback URL,也沒有像 twitter OAuth 那樣有乖乖地提供 pin code 的機制,這樣就有點麻煩。
(你說 mobile app 都沒這問題?對啊,只要乖乖使用 facebook 提供的 sdk,那 callback 的事 Facebook 都幫你搞定了,連 android 都可以幫你生出一個 fbconnect:// 的 URL :P)
不過還好 Facebook 沒有趕盡殺絕,它其實有提供一個簡單的頁面讓你做為 callback URL 來用--http://www.facebook.com/connect/login_success.html,這樣你就不必自己準備 callback URL 了。

不過接下來的關鍵就是,我程式要如何判斷使用者已經完成授權了呢?一個最簡單的作法就是看看使用者是不是已經到了 http://www.facebook.com/connect/login_success.html 這個頁面,若已經導到這個頁面,那就代表使用者已經完成授權,程式可以繼續取得 access token 的步驟,在 Chrome ext/app 中可以這樣做:

/**
* 簡單說明一下,這個做法一樣是開新 tab 導向認證授權頁
*/
var authTabId; // 新開的 tab id
var url = '......'; // facebook 認證授權的 URL

// 開新 tab 帶使用者做授權
chrome.tabs.create({url: url, selected: true}, function(tab){
// 記住新開的 tab id
authTabId = tab.id;
});

// 監控目前 chrome 上的 tabs
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
// 如果新開的 tab 在讀取完成的狀態
if (tabId == authTabId && changeInfo.status == 'complete' &&
tab.url 開頭是 'http://www.facebook.com/connect/login_success.html') {
// 把 login_success 那一頁關掉
chrome.tabs.remove(authTabId);
// TODO: 從 URL hash string 取出 verifier
// TODO: 繼續取得 access token
}
});

參考資料:Authentication - Facebook Graph API

Google 的 OAuth

目前 Google Data API 也支援使用 OAuth 的機制來作授權,不過 Google OAuth 就是最一般的 OAuth 流程,也沒有 pin code 或是像 facebook 有頁面來作弊取巧,不過其實 Chrome ext/app 本身就會有一個 URL:chrome-extension:/// 這樣的形式,你只要在套件中加入一個像是 oauth_callback.html 的檔案,再把 callback URL 指向 chrome-extension:///oauth_callback.html 就可以做完 OAuth 的流程了。

....

目前上述的方式都是可以運作完成 OAuth 流程的,如果各位有不錯的作法或是更簡單的解決方案也還請不吝賜教。

  • Tim Wu

    十分感謝, 這篇文章解決我上個月的疑惑, 之前用python實作facebook oauth,
    facebook 文件裡對 callback url一直舉例 http://www.example.com, 我就覺得奇怪, 不架站就不給auth嗎? 害我還真的去生個server來接callback.

    另一方面我同事寫facebook SDK for iOS 寫, 連什麼是callback url都不知道就完成了, 害我超糗.

  • 其實google在chrome的插件的api有新增oauth部份 http://code.google.com/chrome/extensions/tut_oauth.html 靠這個真的輕鬆很多……

  • Cool

  • Abc

    pp

  • 現在多了一個 jsOAuth (http://bytespider.github.com/jsOAuth/) 套件了,在使用習慣上和 jQuery 挺接近的!