這篇筆記主旨在介紹OAuth 2.0及其資安觀念、伴隨的資安風險

整篇文章將分成五個部分

  • Introduction
  • Authorization code
  • Implicit flow, PKCE
  • OpenID Connect
  • Security Risk on OAuth 2.0

Introduction

開放授權(OAuth)是一個開放標準,允許使用者讓第三方應用存取該使用者在某一網站上儲存的私密的資源(如相片,影片,聯絡人列表),而無需將使用者名稱和密碼提供給第三方應用,現行的版本為OAuth 2.0

下面的例子都將以Yelp(一個第三方程式)跟Google來做說明

為什麼會有OAuth?

假設今天你用Yelp,他需要你Google聯絡人裡的資料,你有可能將Google帳號跟密碼都給Yelp讓他自己進去找嗎?那你乾脆帳密給我,我幫你找好了XD
今天如果你相信Google,並且某種程度來說相信Yelp,想要Yelp只能取得我Google聯絡人裡的資料,而無法取得例如我雲端硬碟的資料、Gmail信件,那這時候OAuth就派上用場了

也就是說,我們希望有一張像這樣子的流程圖

Pic 1

  1. Yelp需要我們Google聯絡人裡的資料,所以叫我們連上Google,進行登入
  2. Google問我們是否要允許Yelp取得我們聯絡人裡的資料,選擇是或否
  3. 選擇是,回到yelp.com/callback,Yelp得到權限,可以去取得我們Google聯絡人裡的資料

Authorization code

繼續下去之前,先來介紹OAuth 2.0中的一些專有名詞

名詞 解釋
Resource owner 資源的擁有者 e.g. 正打算使用Yelp的我們
資源指的是第三方程式想要取得的資源 e.g. Google聯絡人裡的資料
Client 第三方程式
e.g. Yelp
Authorization server 授權伺服器
e.g. accounts.google.com
Resource server 資源伺服器,擁有我們Google聯絡人資料的伺服器
e.g. contacts.google.com
Authorization grant 一個被授權的證明,證明自己是有被授權的,如果第二步驟選擇「是」就會拿到
Redirect URI 重新導向的URI,當我們在第二步驟選擇「是」或「否」之後,瀏覽器要去到哪個網站
e.g. yelp.com/callback
Access token Client真正需要的東西,Client可以用這個token來取得他要的東西,以這裡來Yelp要的東西就是我們Google聯絡人裡的資料

現在,我們將這些名詞帶入整個流程圖

Pic 1.5

  1. Yelp需要我們Google聯絡人裡的資料,所以叫我們連上Google,進行登入
  2. Google問我們是否要允許Yelp取得我們聯絡人裡的資料,選擇是或否
  3. 選擇是,拿到Authorization code,回到yelp.com/callback
  4. Yelp拿著Authorization code去向Google領取Access token
  5. Yelp拿著Access token去找contacts.google.com拿我們Google聯絡人裡的資料

P.S. 第一個步驟那邊有個Response type: code,這是表示請Google在第三步驟給我們Authorization grant的時候以Authorization code的形式給我們,像串代碼一樣 e.g. oMsCeLvIaQm6bTrgtp7


解釋了整個流程之後,再來介紹兩個OAuth 2.0中的專有名詞

名詞 解釋
Scope 權限,像是contact.read (讀聯絡人資料的權限), contact.write (寫聯絡人資料的權限), email.delete(刪除信件的權限) …
Consent 同意,也就是第二步驟,Google在我們是否要允許Yelp取得聯絡人裡的資料上徵求我們(Resource owner)的同意

現在,我們將這兩個專有名詞也帶入流程圖,這次只講有改變的地方

Pic 2

  1. 第一個步驟除了Response type: code之外,加了Scope:profile contacts,表示Yelp跟Google說他想要的權限是個人資料還有聯絡人,有了Access token我應該要有可以跟你拿到這兩項的資料的權限
  2. 第二步驟,前面有提到,就是Google在我們是否要允許Yelp取得聯絡人資料及個人資料上徵求我們(Resource owner)的同意

這時候肯定會有人有問題,肯定有吧?沒有的話代表你可能前面沒想清楚🤪

為什麼第三步驟Google要給我Authorization grant?直接給我Access token不就好了?

要回答這問題,就要再來介紹兩個OAuth 2.0中的專有名詞

名詞 解釋
Front channel (less secure channel) 這就是我們瀏覽器的通訊通道,瀏覽器本身可能是滿安全的,但是當我們在傳request時,別人可以從我們後面看到我們的瀏覽器在幹嘛 (shoulder surfing)、網址列上面顯示了什麼。除此之外,我們在瀏覽器上安裝的外掛程式、網站上的第三方的腳本可能也紀錄了我們做什麼request,比起直接從伺服器發出request並得到response,我們沒辦法那麼相信瀏覽器上的安全性跟收到的結果
Back channel (highly secure channel) 從我的後端伺服器對其他的伺服器做出request e.g. HTTPS POST Request,那這算是一個高度安全的通訊通道 (跟Front channel相比),我們就稱之為Back channel

再看一次流程圖,實線代表我們在Front channel中做的事、虛線則代表Back channel

Pic 2

  1. 連上Google,進行登入
  2. 選擇是或否
  3. 拿到Authorization code

以上的流程都是實線,並不安全,其他人可以藉由上方Front channel解釋中所說的方式得到想要的資訊

現在,思考一下

Pic fun

別人可以看到我們的Redirect URI,那怎樣?可以幹嘛嗎?不行❌

別人可以看到我們的Scope,那又怎樣?可以幹嘛嗎?不行❌

但是回到剛剛的問題
為什麼第三步驟Google要給我Authorization grant?直接給我Access token不就好了?

第三步驟給你Access token,別人可以看到我們的Access token,那又怎樣?可以幹嘛嗎?可以❗️❗️❗️🙀

這樣子Access token就很容易被別人用惡意腳本、外掛,甚至站在你後面瞬間記憶背下來,直接跑回座位用那個Access token去跟Google拿你的資料

這就是為什麼在第三步驟給你Authorization grant,並且第四、五步驟在Back channel中完成(虛線),不會顯示在你的瀏覽器上,這樣Access token就比較不容易洩漏

那這時候,你應該要有另一個問題,沒有問題的話可能代表你前面真的沒聽懂🤪

那這樣不就代表我在第三步驟拿到的Authorization code不夠安全嗎?因為他在Front channel

答案是對,不夠安全

那差別在哪?我一樣可以用惡意腳本、外掛,甚至站在你後面瞬間記憶背下Authorization code去跟Google要Access token啊

所以更詳細的流程圖是這樣

Pic 3

在第四步驟,附帶一個secret給Google,可以看作類似密碼的東西,讓他確定我們真的是Authorization code的擁有者,而不是從別人那裡偷來的Authorization code,這樣的話,即使Authorization code不是那麼安全,但Access token就很安全

整個OAuth流程開始之前,Client跟Authorization server(這裡是Yelp跟Google)就應該要知道你的secret是什麼,才有辦法在第四步驟進行傳送跟驗證

這裡再介紹一個OAuth 2.0中的專有名詞

名詞 解釋
State 通常是一個隨機的字串,設定好之後在第一步驟時跟著送出,並應該在第三步驟原封不動地送回來,有這個東西可以防止CSRF(Cross-Site Request Forgery)。簡單來說,我們在第三步驟會去對照送來的state跟我們之前送出去的state是不是一樣的,如果是一樣那就沒問題,接受Authorization code; 但如果是不一樣的,那就代表有人可能假裝是我們去做了第一步驟,這時就不應該接受這個Authorization code

實際Request看起來是這樣

Pic 4

去accounts.google.com,跟他說我是abc123,結束後我要回到https://yelp.com/callback,我要求的是讀取我個人資料的權限,我希望你能給我code,state我設定成foobar

Pic 5

  1. 如果你登入後選擇否,或是第一步驟給的東西有錯誤或漏給,那回到https://yelp.com/callback時就會有error跟錯誤訊息。像這裡是The user did not consent. 代表是你按了否而產生的錯誤
  2. 如果你登入後選擇是,那回到https://yelp.com/callback的時候就會拿到code和state。state依然是foobar,那代表這個code的確是我們申請的不是別人假冒我們身份跑去申請的

Pic 6

第四步驟,Yelp拿著code,跟Google說我是abc123,我的secret是secret123,我手上拿的是Authorization code,我想要領取Access token

Pic 7

如果通過驗證,Yelp就會得到Google的回應,「這是你的Access token,這個token的類型是Bearer,他在3920秒後會過期不能使用」

Pic 8

第五步驟,Yelp去對應的服務,帶上Access token去做被授權可以做的事情(比方說讀取我的個人資料),那對方會確認這個Access token是不是有效的,然後確定權限是對的,就會讓我們存取


Implicit flow, PKCE

OAuth 2.0取得授權的流程其實有四種,上面介紹的是最主流且完整的Authorization code的方式

Pic 9

下面兩個只用Back channel的就不多做介紹,不是那麼常見

接下來要介紹的是Implicit的方式,他只有Front channel,因為他可能是個Single-Page App或是Mobile App,像是Pure Javascript, React, Angular App等等,他並沒有後端伺服器可以去做Request或儲存你的secret,意味著沒有Back channel,那我們要做的事就必須都在Front channel上完成

那下面這個就是Implicit的流程,可以看到幾乎都一樣,改變的地方是

Pic 10

  1. 第一個步驟中Response type: code改成Response type: token,讓Google知道我們在第三步驟想要直接拿到Access token,不是Authorization code
  2. 第三步驟中,我們直接拿到Access token,可以直接到第五步驟去用Access token做符合權限的操作

如前面所提,這樣相對不是那麼安全,但早期 (2010年左右),這就是使用Single-Page Apps或Mobile Apps這種缺乏Back channel情況下的最佳解

隨著瀏覽器持續在進步,Implicit不再是我們對Single-Page Apps或Mobile Apps所擁有的最佳作法,現在我們可以使用PKCE (Proof Key for Code Exchange) 這個擴充功能,來提升安全性

差別在哪?先來了解PKCE中的專有名詞

名詞 解釋
Code verifier 一個隨機的字串
Code challenge 取Code verifier的SHA256 hash值並做url-safe的base64編碼所得到的字串

比方說
如果Code verifier是5d2309e5bb73b864f989753887fe52f79ce5270395e25862da6940d5 那我們可以產生的Code challenge長這樣
MChCW5vD-3h03HMGFZYskOSTir7II_MMTb8a9rJNhnI

再來,我們要了解如何使用,我們一樣用Authorization code的流程圖說明

Pic 11

去accounts.google.com,跟他說我是abc123,結束後我要回到https://yelp.com/callback,我要求的是讀取我個人資料的權限,我希望你能給我code,state我設定成foobar

上面都一樣,但這次多了跟Google說code_challenge是MChCW5vD-3h03HMGFZYskOSTir7II_MMTb8a9rJNhnI,並且取Hash的方法是SHA256(S256)

第二三步驟不變,對照state是不是一樣,並拿到Authorization code

Pic 13

第四步驟小改變,Yelp拿著code,跟他說我是abc123,我手上拿的是Authorization code,我想要領取Access token,但這次我不提供secret,取而代之的是,我提供code_verifier 5d2309e5bb73b864f989753887fe52f79ce5270395e25862da6940d5,你可以去驗證看看

Google會去取Code verifier的SHA256 hash值並做url-safe的base64編碼所得到的字串跟第一步驟拿到的code_challenge做比對,如果一樣,那就驗證成功,代表我是同一個人,這個Request是有效的

如果通過驗證,那就拿到Access token

第五步驟一樣,拿Access token去做事


總結來說,我們沒有事先講好的secret,而是在第一個步驟給Google challenge,第四步驟給Goolge verifier讓他去驗證verifier是不是對應到challenge

這時有些人會有疑問,這次不強迫非要有疑問🤪

這些都是在Front channel中進行,那不是一樣不太安全嗎?

可以這麼說,但算是相對安全了,即使challenge不幸被別人看到,其實也還好,url-safe的base64編碼可以馬上逆回去沒錯,但是取SHA256 hash值要逆回去是很麻煩的,今天如果A字串(長度夠且夠隨機)取出來的SHA256 hash值是B,要從B猜出A要花非常多的時間,因此比單純的Implicit方式直接給你Access token來得有保障

PKCE不只可以作為Implicit的擴充功能,也能作為Authorization code的擴充功能(同時給出secret、Authorization code、challenge_verifier才能取得Access token),提供OAuth 2.0更好的安全性


OpenID Connect

如開頭所介紹,OAuth是一個以授權為主要目的的開放標準,但很多人卻把它拿來以驗證為主要目的使用,不是說不行,但這就會產生一些問題,像是它沒有一套標準去規定如何取得使用者個人資料、大家都用不同的方式去實作OAuth上的驗證功能,變得很混亂

Pic 16

因此,OpenID Connect作為OAuth 2.0的驗證擴充功能誕生了


OpenID Connect造就了以下四樣東西及改變

  1. ID token: 儲存一些使用者資訊
  2. UserInfo endpoint: 可以拿Access token去這裡拿更多的使用者資訊
  3. Standard set of scopes: 統一了取得使用者資訊上的權限
  4. Standardized Implementation: 有一套標準去實作OAuth 2.0上的驗證

下面是加了OpenID Connect之後的流程圖,改變的地方是

Pic 17

  1. 第一步驟中,scope多了openid,跟Google說我們需要取得使用者資訊的權限
  2. 第四步驟中,Yelp用Authorization code不只是跟Google要Access token,還跟他要ID token
  3. 第五步驟中,Yelp可以用Access token去UserInfo endpoint(這裏是/userinfo)跟他要更多使用者的資訊

實際Request看起來是這樣

Pic 18

去accounts.google.com,跟他說我是abc123,結束後我要回到https://yelp.com/callback,我要求的是讀取我個人資料的權限,我希望你能給我code,state我設定成foobar

第二三步驟不變,對照state是不是一樣,並拿到Authorization code

第四步驟跟Google要token的方式不變,但回應不一樣

Pic 20

Yelp得到Google的回應,除了Access token還多拿到ID token

第五步驟,Yelp除了帶上Access token去對應的服務做事,也可以用Access token去UserInfo endpoint跟他要更多使用者的資訊

P.S. ID token不是用來跟Google要更多資訊的,而是本身就含有一些使用者資訊,通常是以JWT (JSON Web Token) 的方式來儲存,用jwt.io等工具可以看到裡面的資訊(不是本篇的主題,不深入討論😏)

ID token解碼之後可以看到一些使用者的資訊,像是姓名、email等等

下圖是OAuth 2.0跟OpenID Connect的比較,他們之間並沒有誰比較好誰比較不好,而是兩個為了不同目的而誕生的標準

Pic 21


Security Risk on OAuth 2.0

  1. Unvalidated redirect URI

攻擊者可以攔截Client傳給Authorization server的封包,變更Redirect URI,如果Authorization server沒有確認Redirect URI確實是屬於Client的domain,就可能將含有Authorization code的Response導給攻擊者。攻擊者取得Authorization code之後,就有機會用暴力破解去破secret,進而取得Access token

  1. Weak authorization codes

Authorization code如果不夠安全,有什麼規律性或是邏輯漏洞,就有可能被預測出來

  1. Long-lasting authorization codes

Authorization code要設有適當的有效時間,如果永遠不會過期,有可能被暴力破解或攔截下來後重複使用

  1. Code not limited to one client

我們信任A Client所以讓他跟Authorization server取得Authorization code
用A Client取得的Authorization code不應該可以被拿去給B Client使用,因為我們並不一定信任B Client,B Client很可能是個惡意Client

Reference

OAuth 2.0 and OpenID Connect (in plain English)
What’s going on with the OAuth 2.0 Implicit flow?
OAuth 2 Simplified
OAuth 2.0 Implementation and Security