簡介 OpenID Connect

以目前而言,OAuth 2.0 授權框架(下簡稱 OAuth2)定義了一個相較安全的授權流程。但身分驗證並不在 OAuth2 的範疇內,而今天簡介的 OpenID Connect 正是基於 OAuth2 的基礎上,再另外定義了身分驗證的流程。

OpenID Connect 的協定於 2014 年發布,裡面有用到了 OAuth2 與 JWT,相較 SAML 來說,算是一個非常年輕的協定。

這裡有趣的地方是:OAuth2 為 2012 年發布,JWT 則為 2015 年發布,這是因為 OpenID Connect 是參考 JWT 的草稿文件。

我是使用 ORY Hydra (下簡稱 Hydra)這個開源服務,Hydra 也是 OpenID 官方認證符合協定的實作之一。ORY Hydra 提到他們稱自己的實作為 User Login and Consent Flow,裡面完整了說明身分驗證與授權流程,非常好理解。以下會以 Hydra 的文件為主,來介紹 OpenID Connect。

角色定義

因基於 OAuth2 上實作新的協定,所以有部分內容會跟 OAuth2 重覆

  • Resource owner,同 OAuth2 說明
  • Resource Server,同 OAuth2 說明
  • Client,同 OAuth2 說明
  • Authorization Server,實作 OAuth2 協定的服務
  • OpenID Provider,提供 OpenID Connect 的服務,其實也是指 Authorization Server 加上身分驗證功能等的服務
  • Login Provider,Hydra 定義為提供登入介面,與實作功能的服務
  • Consent Provider,Hydra 定義為提供使用者授權介面,與實作功能的服務

主要流程

時序圖如下:

@startuml
UserAgent -> Client: Access protected resource
UserAgent <- Client: (1) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (1) Initial Authorization Code Flow or Implict Flow
UserAgent <- AuthorizationServer: (2) Redirect to Login Provider
UserAgent -> LoginProvider: (2) Challange and Authenticate user
LoginProvider <--> AuthorizationServer: (3) Fetch login info && accept login
UserAgent <- LoginProvider: (3) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (3) Check login ifno
UserAgent <- AuthorizationServer: (4) Redirect to Consent Provider
UserAgent -> ConsentProvider: (5) Request grant
ConsentProvider <--> AuthorizationServer: (6) Fetch scope info
UserAgent <- ConsentProvider: (6) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (6) Check consent info
UserAgent <- AuthorizationServer: (7) Redirect to Client
UserAgent <- Client: (7) With Code
Client --> AuthorizationServer: (8) Send token endpoint
Client <-- AuthorizationServer: (8) Get access token and id token
@enduml

流程描述如下

  1. 首先 Client 需要啟動一個 OpenID Connect 流程,實務上的做法是把使用者轉導到 http://hydra/oauth2/auth?client_id=...&...
  2. Hydra 如果發現使用者尚未驗證的話(也就是沒有 session 或 cookie 等),會把使用者轉導到 Login Provider。上面有提過,Login Provider 的任務是提供使用者登入介面,如帳密輸入之類的畫面。這時候的 URL 會類似像這樣:http://login-provider/login?login_challenge=1234...
  3. Login Provider 將會自己處理登入流程。登入成功後,需要把使用者的資訊(如 user ID)傳送給 Hydra。接著再轉導回 Hydra:http://hydra/oauth2/auth?client_id=...&...&login_verifier=4321
  4. 回到 Hydra 後,Hydra 會再把使用者導去 Consent Provider,如:http://consent-service/consent?consent_challenge=4567...
  5. Consent Provider 將會顯示要求使用者授權 Client 哪些權限(也就是 OAuth2 的 Scope)的介面。
  6. Consent Provider 在使用者完成授權時,會發出另一個請求給 Hydra,讓 Hydra 知道 Scope 有哪些。接著再轉導回 Hydra:http://hydra/oauth2/auth?client_id=...&...&consent_verifier=7654...
  7. 接著再依 Hydra 的轉導,回到 Client。
  8. 此時,使用者已完成認證與授權。最後,Client 即可跟 Hydra 要 Access Token、Refresh Token、ID Token。

這裡因為有多個服務傳來傳去的很複雜,下面額外說明細節。

Client 啟動 OpenID Connect 流程

與 OAuth2 類似,但 OpenID Connect 額外的定義如下:

  • 發送給授權伺服器的請求,可以是 GETPOST
  • Scope 必須要有 openid
  • 額外定義了其他參數如 response_modenonce

下面是以 Line 為例的 GET 請求:

https://access.line.me/oauth2/v2.1/authorize?response_type=code&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fline%2Fcallback&state=40c798406415ea0409416d76c2596430&client_id=1234567890

Login Provider 處理身分驗證

若 OpenID Provider 還不認識使用者的話(指沒有 session 或 cookie),則會要求使用者做身分驗證。在 Hydra 的流程定義裡,Login Provider 的任務即專心做身分驗證即可,符合單一職責原則,這裡回頭看一下過去提到的身分驗證可以怎麼做有提到哪些方法:

其中 API 身分驗證跟此情境比較不符,所以不參考。但帳號密碼驗證或第三方身分驗證都是可以在 Login Provider 實作的,這代表 OpenID Connect 登入的過程,可以再繼續使用其他的 OpenID Provider 做身分驗證。舉個例子,如:使用 Hydra 做為 OpenID Provider,同時 Login Provider 提供帳號密碼驗證,也提供 Line 登入。

Hydra 的做法是將 Login Provider 分離,而我們來看看 Line,當 Client 發出授權請求時,Line 會將使用者導去驗證的 URL 如下:

https://access.line.me/oauth2/v2.1/login?returnUri=%2Foauth2%2Fv2.1%2Fauthorize%2Fconsent%3Fscope%3Dopenid%2Bprofile%26response_type%3Dcode%26state%3Db1e4221e89abe958df760cb8bcada23e%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8080%252Fline%252Fcallback%26client_id%3D1653300970&loginChannelId=1234567890&loginState=Rb31VRbWP2ubLbMbxiQGC

這裡是 Line 登入的畫面。

從 URL 可以大略看得出來,https://access.line.me/oauth2/v2.1/authorize 是在處理 OAuth2 授權請求,而 https://access.line.me/oauth2/v2.1/login 則是處理身分驗證,這概念與 Hydra 所提到的 Login Provider 是雷同的。

在驗證完身分後,接著就會繼續走 [OAuth2 的授權流程][Day 24]。其中有一步是要求使用者授權,RFC 6749 裡面,而 Hydra 則是提到要額外一個叫 Consent Provider 的服務來處理。這個服務可以把 Client 所請求的授權列在介面上讓使用者確認。

以 Line 來說,Consent Provider 是實作下面這個 URL 的:

https://access.line.me/oauth2/v2.1/authorize/consent?scope=openid+profile&response_type=code&state=b1e4221e89abe958df760cb8bcada23e&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fline%2Fcallback&client_id=1234567890

這裡是 Line 授權的畫面。

openid 也是個 Scope,Line 對於它的說明為:

用戶識別資訊(必要資訊)

由LINE指派的唯一內部識別資訊

Consent Provider 與 Login Provider 類似,服務是實作在另一個路徑上的,下面把 Line 三個端口再列表一次:

endpoint URL
Authorization Endpoint https://access.line.me/oauth2/v2.1/authorize
Login Provider https://access.line.me/oauth2/v2.1/login
Consent Provider https://access.line.me/oauth2/v2.1/authorize/consent

從 URL 約略可以了解,這可能是做在同個服務上的。

而 Hydra 的設計是把 Login Provider 與 Consent Provider 抽象化,也就是它定義了這兩個 provider 要做什麼事,以及如何與 Hydra 交換資訊。這樣設計的好處是:

  1. Hydra 的任務非常簡單,只要控制好流程即可
  2. 因為是抽象化,這代表實作可以是全新的服務,也可以是即有服務,這對舊系統要建構 OpenID Provider 是有利的

取得 Token

在最後授權完成後,使用者會將 code 轉傳給 Client,Client 即可拿 code 去跟授權伺服器換 token。換 token 的方法與 OAuth2 一樣,需要帶 client_idclient_secret,但回傳內容會多了 ID Token,格式即為 JWT。以 Line 為例,裡面解出來的 claim 內容如下:

{
"iss": "https://access.line.me",
"sub": "a1b2c3",
"aud": "1234567890",
"exp": 1570783265,
"iat": 1570779665,
"amr": [
"linesso"
],
"name": "Miles Chou",
"picture": "https://profile.line-scdn.net/xxxxx"
}

裡面 iss 很明確說明了這個 JWT 是由 Line 發行的;aud 則說明了這個 JWT 是要給 1234567890 這個 Client 使用的,這會跟一開始發送請求時帶的 client_id 參數相同等等。

有的資訊之前有提過就不再重覆。

小結

到目前為止,配合 Hydra 的 User Login and Consent Flow 以及實際串接 Line 的過程,即能大概了解 OpenID Connect 的運作原理。

Line 是使用 Authorization Code Flow,所以剛剛的說明主要都是在描述 Authorization Code Flow 的流程。

參考資料