7個小時與Linkedin OAuth的戰鬥

最近接了一個case,case的其中一項目標是要實踐使用Facebook, Gogle, Linkedin來登入網站。

上網查了一下,有朋友推薦使用 oauth.io 這個服務。號稱可以幫你整合Facebook, Google, Linkedin, Yahoo, Twitter…..所有你想得到的OAuth Provider,於是就很開心的申請一個帳號來用。free版一個月可以接受1000次不同帳號的OAuth API操作,對一個小網站來說應該是很夠用了。因此就不假思索的開始研究。

不得不說,OAuth.io 很簡單,照著官方網站的操作把Facebook, Google, Linkedin的Private key和Domain指到OAuth.io,最後利用OAuth.io的javascript Library取得access_token。就可以向Service Provider取得資料授權。

花了15分鐘把Facebook搞定,花了另外15分鐘把Google搞定,確定可以取得access_token,But……人生很多問題就出在這個But啊

設定Linkedin授權給OAuth.io後,熊熊發現,為什麼Linkedin的access_token長得和別人不一樣?

oauth.io for linkedin

是的,沒有為什麼,那是因為,明明Linkedin已經使用了OAuth2.0,但是OAuth.io並只支援到OAuth1.0啊。

接下來就是悲劇的開始,現在眼前面臨了兩個選擇 1. 研究OAuth1.0,利用OAuth1.0來完成授權 2. 放棄OAuth.io for linkedin,手動利用OAuth2.0授權

為了要維持程式的一致性,所以很自然的選擇了第一種作法,但是,萬萬沒想到這個決定,竟然會讓開發者,在接下來的四個小時,跌入了痛苦的深淵(盛竹如口吻)。

相較於OAuth2.0,OAuth1.0是非常討厭的東西……看了Slideshare的這份投影片,你就會理解我在說什麼

OAuth相較於OAuth2.0的簡單取得access_token,再利用private key向Service Provider伺服器驗證並交換user data,OAuth1.0必須要在開發者這邊先做好資料簽章,方法是使用HMAC,簽章的對象是你想要取得資料的網址。你可以去 Linkedin 的 Test Conosle 玩耍一下,大概會知道我在說什麼。

讓我們直接跳過一連串的廢話,重點是: 「OAuth1.0非常麻煩」,但不論再怎麼麻煩,總是會有範例的啊,問題是,Linkedin for python user 提供的範例 是使用python2,官方建議的套件可以在這裡找到。

天殺的python2啊~~~林北使用的是最新最潮的python3啦,稍微找了一下,發現其他支援的套件操作方式都沒有Linkedin官方提供的簡單,於是我又下了一個錯誤的決定:

「手動把套件轉成Python3 support!!」

於是又稍微研究了一下怎麼把python2的檔轉換成支援python3,看起來是簡單的下 2to3 就好了,然後稍微檢查一下import library支援性的問題。費盡一番心血把這些問題處理好後,把程式碼安插到我的專案,開始測試到底有沒有辦法從Linkedin那邊抓到資料。

卻發現,Linkedin有回應(淚),但是授權錯誤根本抓不到。

然後又是一連串研究到底是哪裡設定錯了,看起來根本沒錯啊,於是重新比較Linkedin的test console,讓我懷疑,該Library對Linkedin的簽章方法過時,可能Miss掉一些東西,導致簽章一直過不了。

看到最後我也不想去改Source code了……那本身就是一個悲劇。在悲劇上花費心力對你的人生一點幫助都沒有。

於是,我放棄OAuth1.0,放棄OAuth.io對Linkedin的support,開始手動寫OAuth2.0的code。

相較於折騰人的OAuth1.0,OAuth2.0好多了,但是有幾點小細節要注意,因為Linkedin的官方文件真的很容易誤導人,像是我卡在這邊浪費一個多小時

你可以在Linkedin官方OAuth2.0 step by step教學中看到這段話

linkedin oauth2.0

是的,你要對Linkedin使用https,這個我有注意到,然後要使用POST,嗯,這個我也注意到

但是請「千萬要記得」,正確的作法是對整個網址POST!!!不是把granttype, code, redirecturl等東西包起來當成資料對以前的網址做POST啊

如果你的作法和我或是這個國外網友一樣愚蠢的話,會收到這樣的錯誤訊息

{“error”:”invalidrequest”,”errordescription”:”missing required parameters, includes an invalid parameter value, parameter more then once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired”}

他馬的誰會這樣做啦(崩潰)沒事對這種東西做POST幹嘛……為什麼要把參數都寫在網址上再用POST,這不是理所當然要用GET嗎(已哭)

突破這一關後,你還有最後一個難關。

接下來,Linkedin會一直授權失敗,

查詢問題後發現問題在於如果你有使用Linkedin OAuth1.0過,那OAuth2.0會一直過不了。有國外網友很神奇的解決了這個問題,那就是到Linkedin的設定頁面

Selection_012

按下Revoke,撤銷API key 和 Secret Key,之後重新設定新的API key和Sercret Key,一切才大功告成。

謹以本文,奉獻給未來踏上Linkedin的朋友誠摯的建議

  1. for Linkedin,不要用OAuth1.0。
  2. 用OAuth2.0如果發生問題或錯誤,請服用本文解決,人類的知識來自於眾人的分享與累積,不要讓我的七個小時白白浪費掉啊。

10分鐘理解OAuth和facebook登入原理

最近接了一個要求使用facebook做登入的案子,因此花了好些時間研究facebook登入如何實作。決定留下紀錄,供日後步上相同道路的人參考。

首先,要先理解OAuth是拿來做什麼的,才能知道如何利用他。常聽到OAuth和OpenID,最簡單的說法就是

OpenID:用作「身份認證」,如果某網站support OpenID,你登入其他網站不需要記憶密碼,只要拿你的OpenID登入就好。

OAuth:用作「授權」,舉例而言,如果你需要授權某相片列印公司的網站取得你在Facebook的相片,只要該相片列印公司support OAuth,你也授權,那該公司就可以取得你在Facebook的相片。

今天不談OpenID(因為我也還沒研究),只談OAuth。直接講重點,究竟OAuth要怎麼進行授權?

OAuth有分1.0和2.0,我們只討論比較新的2.0

首先,分四個角色

  • 資源擁有者(Resource Owner): 可以想成擁有Facebook帳號的小明。
  • 授權伺服器(Authorization Server): 相片列印公司向facebook取得使用者授權的伺服器
  • 資源伺服器(Resource Server): 保管你facebook相片的server
  • 用戶端(Client): 想要取得你facebook相片的列印公司

整體的流程是:

首先,相片列印公司在facebook有一個應用程式。

有一天,小明想要到哇哈哈相片列印公司網站想要列印自己的生日Party照片,哇哈哈必須要取得小明的facebook帳號授權,才可以取得小明在facebook的照片。

因此哇哈哈會把小明重新導向到facebook,小明必須向facebook說明「我授權給哇哈哈相片列印公司」,之後facebook會告訴哇哈哈的網站「我已經取得小明的授權了」,證明的方法是發送一個授權token給哇哈哈的網站。

但這時哇哈哈不能確定這個授權token真的是facebook給的,可能是某個不懷好意的無聊資工系學生(相信我,這種人很多)對你的網站進行練習資訊安全作業惡搞你。因此哇哈哈必須要把這個token送回給facebook,讓facebook告訴哇哈哈這個授權是真的,如果是真的,facebook會交給哇哈哈一串「access_token」,這個東西就是萬能的鑰匙!哇哈哈公司用這個就可以向facebook取得所有小明授權的資料了!

在 Facebook 利用OAuth 登入使用者

facebook並沒有提供OpenID服務,只提供OAuth,因此雖然OAuth的用途不是身份認證,但我們可以利用OAuth的特性來登入使用者。

接下來我們直接來看facebook文件吧。

facebook登入有分JavaScript登入,由Facebook提供SDK,都幫你包好了。或者你可以手動自己刻Server Login。但說真的,雖然我有用過Facebook JavaScript Login,但原理我沒有很清楚,一下子就突然登進去。所有取得授權資料code都是javascript,不過直接把程式碼暴露在前端還是怕怕的,如果對安全性比較要求的話還是自己慢慢刻後端登入吧。

手動登入Facebook官方文件

接下來請一邊參考官方文件,一邊參閱我的解釋,這樣學習的效果最好。

首先,你必須在Facebook建立一個應用程式。建立完後啟動網站登入功能,會得到

Selection_003

首先,你的網站必須把使用者重新導向回facebook,讓使用者告訴facebook可以授權給你。

因此建立一個取得授權的連結如下:

Selection_004

client_id當然就填你的應用程式ID, redirect_uri填入facebook授權完之後,要告訴你網站的哪個網址。其他還有一些參數可以設定自己看文件啦。

如果授權成功, facebook會向你給的redirect_uri傳一串code給你,這個code就是使用者授權給你家網站的證據。但問題是,你不能確定這個code真的是facebook傳回給你的,老樣子,網路是很危險的。

因此你必須要拿這個code向facebook換access_token

Selection_005因此,請自行在後端向facebook發出這個請求,記得填入你的app-id和app_screte key, 如此一來才能向facebook證明你就是這個應用程式的擁有者。之後facebook等待facebook的response。記得,這裡的redirect_uri必須要和上面的取得授權的uri相同,不然facebook會覺得你這個人怪怪的。

之後如果驗證成功,facebook會response你一個access_toke,這個就是你費盡千辛萬苦終於得到的facebook鑰匙,憑此就可以到授權給你的人的facebook相簿拿取他的相片了。如果錯誤的話,facebook會回傳你一段json格式的錯誤訊息。但正確的話,會回傳給你一串

Selection_006字樣,上面還會寫expires time(單位是秒), 要盡早使用,不然會過期。

取得access_token之後,請保存好,這就是這個人的授權鑰匙,你可以拿這個access_token去問facebook這個人是誰,facebook就會告訴你,因此你就把該名使用者資訊寫到database註冊這名使用者。

利用access_token 取得資訊的方法請參閱官方文件 使用facebook graph api,建議可以利用graph explorer玩一玩,就可以知道利用access_token換取使用者資料。

同樣的,之後該名使用者如果再度來到你的網站,你一樣可以問facebook該名使用者是誰,然後檢查是否資料庫已經存在該名使用者的資料,如果是的話,就可以把該名使用者登入進去你的網站。

以上,就是嘗試對facebook OAuth做簡單的說明,旨在讓不熟悉的人能快速了解整個facebook手動登入的全貌,如果有錯誤歡迎大家批評指教留言更正。

參考資料:

2013 回顧,該重新開始寫網誌了。

很久沒有動筆寫網誌了,高中時還有在寫,紀錄自己高中生活的點點滴滴,上了大學習慣 Facebook 之後,網誌就被幾句短短的即時動態取代了,當我滾著 facebook 開始回首這一年,總覺得好像有什麼東西被遺忘似的,人生很多事情不是三言兩語就可以取代的啊。

12/31晚上看完清大的跨年煙火後,回寢室翻著fb動態牆,逛到強者我學長大兜和強者我學弟pellaeon的網誌,看著他們兩人的年度回顧,不禁有些慚愧。

1/1元旦放假,陽光普照。我循著清交小徑走到清大,坐在大草皮上望著遠方,思考這一年來我有什麼成長,有什麼收穫,有什麼需要改進的地方。

這一年,參加了tic100商業模式競賽、和一群好朋友努力到複賽,花了3個月,還曾一起連續開會11個半小時(人生最長開會時間成就達成),雖然最終還是被淘汰,但是認識一群會永遠記得的好夥伴。

這一年,第一次上街頭,和25萬人一同悼念一個陌生人。只因為相信,正義不該被黑幕所掩蓋,正義需要人們挺身而出去守護自己想守護的東西。

這一年,程式寫得更多了,寒假時想要改變學校同學使用行事曆的方式,和好朋友一起進行F4 Calendar計畫,但努力了兩個多月最後因為技術力與對目標使用者瞭解不足失敗告終、暑假到Sam那邊實習,研究保險與寫程式,9月開學受朋友所托,幫忙校內的wakeupbar寫官方網站。10月到12月,前端後端幾乎全包的自幹嚮茶飲料DIY計畫的網站,寫到很崩潰但最後還是趕出來了。

這一年,達成了很多人生成就,第一次衝浪被大海制裁、第一次溯溪從7米高的地方跳潭、第一次換SSD覺得用電腦怎麼可以比大便還順暢、第一次手沖咖啡發現其實還不賴、第一次打cs沒被隊友爆頭感到非常愉快。

自己還是那個整天嚷嚷要改變世界的小鬼,只是對自己的能力感到非常慚愧。每次寫程式遇到問題,上網google不到解答,或是陷入文件海裡,花了大半天還是找不到正確的設定,總是會開始懷疑自己究竟適不適合寫程式。看看周遭強者朋友寫程式像喝水般飛快,聊著許多一時半刻我無法明白的專有名詞,自己的自信彷彿也跟著沉到水裡一般。

但一想到那些強者比我花了起碼三年以上的時間在程式碼中遊走,又會覺得一切都理所當然。我大一開始寫程式到現在都還不滿三年啊。每件事情都有他的代價,付出不夠又怎能奢望回報呢?

這一年,開始相信生命是長期而持續的累積,相信付出的努力不會白費,總是累積出一些東西。持續去健身房14個月,感受自己身體的變化,一點一滴慢慢變強壯,血液循環變好,變得不太怕冷,雖然每次練完腿都很想死,但是卻覺得人生哪有比運動更簡單,只要付出就有報酬的事情呢?咬咬牙撐過去就是了。

要檢討的話,這一年的自己過得太安逸了,躲在自己的舒適圈內,做自己喜歡的事情。人生不該只侷限在大學內,應該要盡可能的去挑戰與嘗試不同的環境。暑期實習兩個月讓我感受到工作的莫可奈何,大學生活應該要有更多嘗試與刺激才行。。

給自己立下新一年的期許吧:「敞開心胸、接觸更多不同事物、認識並瞭解更多人,最後,持續付出努力,有一天你會突然發現,自己不再是那個整天妄自菲薄的自己。」

引體向上

然後,要開始寫網誌了。