# OIDC(OpenID Connection) ###### tags: `OIDC`,`OAuth` OIDC 是一種基於 OAuth 2.0 的身份認證協議,允許使用者端通過 Authorization Server 驗證使用者身份,並以標準的 `ID Token` 獲取使用者的基本資訊。 OAuth 只討論如何授權(authorization),並沒有明確定義身分驗證(authentication)機制,而 OIDC 協定補足了這部分缺漏。 ## 身份 ### UserAgent (ResourceOwner) 資源擁有者,基本上就是使用者 ### OIDC Client 想要取得 user 認證及授權的 client 端,必須在 Authorization Server 有註冊。 ### Authorization Server / OpenID Provider - 身兼驗證跟授權功能 - 支援 SSO - 提供 OPID Client 註冊申請,至少需要以下資料 | Attributes | description | 範例| | ------ | ----------- | ----------- | | client_id | OIDC Client ID | awesome-service | | audience | 允許接受 ID Token 的 URI | https://awesome.burgess.com | | redirect_uri | 登入後轉導的頁面 |https://awesome.burgess.com/login/callback | | backchannel_logout_uri | 主動發送登出請求給 client 端的 URI | https://awesome.burgess.com/logout/callback | | post_logout_redirect_uris | 登出後轉導的頁面 | https://awesome.burgess.com/logout/backchannel | 註冊後 Authorization Server 會發給 client 端一個 `client_secret`,之後換 `ID_TOKEN` 會用到 - [ORY Hydra](https://www.ory.sh/docs/hydra) 提供實作 OAuth 2.0 + OpenID Connect Provider,server 接口可參考其 [Hydra HTTP API documentation](https://www.ory.sh/docs/hydra/sdk/api) ## 基本的登入 flow | 路徑 | 說明 | | ------ | ----------- | | GET /oauth2/auth | 取得 Authorization Code | | POST /oauth2/token | 取得 `ID Token` | ```sequence loginFlow "User Agent"->"OIDC Client":登入 "OIDC Client"->"User Agent":(1)回傳 state, scope "User Agent"->"Authorization Server":(2)call /oauth2/auth 取得 Authorization Code "Authorization Server"->"Authorization Server":使用者帳密驗證 "Authorization Server"->"User Agent":(3)回傳 302 & redirect_uri 與 (4) 所需參數 "User Agent"->"OIDC Client":(4)redirect to redirect_uri "OIDC Client"->"OIDC Client":(5)驗證 state "OIDC Client"->"Authorization Server":(6)call /auth2/token 取得 `ID Token` "Authorization Server"->"OIDC Client":(7) 回傳 ID Token & access token "OIDC Client"->"User Agent":登入成功 ``` 登入成功後就可以拿 `ID Token` 去取得 user info ### (1) 回傳 state, scope state 由 OIDC Client 隨機生成,之後步驟可與 Authorization Server 回傳的 state 比對,目的是為了確認轉導過程中的使用者都是同一個人,防止 CSRF 攻擊。 scope 為這次 OIDC Client 想取得使用者的 scope,依 [ORY Hydra Authentication](https://www.ory.sh/docs/hydra/sdk/api#authentication) ,登入驗證並取得 ID Token 的 scope 是填 `openid` 步驟 (2) 其他所需的參數可在這回傳。 ### (2) call /oauth2/auth 取得 Authorization Code 透過 GET 方法發送至 authorize endpoint GET 參數 | parameters | description | | ------ | ----------- | | client_id | OIDC Client ID,要跟 Authorization Server 註冊的一致| | nonce | 隨機字串,驗證後會存在 ID Token 內 | | backchannel_logout_uri | 主動發送登出請求給服務的 URI | | redirect_uri | 登入後轉導的頁面,要跟 Authorization Server 註冊的一致 | | response_type | 回應類型,通常固定填 code| | scope | 登入驗證並取得 ID Token 的 scope 為 `openid`| | state | 為避免 CSRF 攻擊的一個隨機字串 | ### (3) (4) 回傳 Authorization code, state 及授權 scope 到 redirect_uri Authorization Server 會回傳一個 302 轉址到 redirect_uri 的訊息給 UserAgent,轉址內容含有 Authorization code, state and scope 等參數。 redirect_uri 必須是該 Client ID 在 Authorization Server 註冊的 uri ### (5)驗證 state 步驟 (1) OIDC Client 有回傳 `state`,這時可驗證從 Authorization Server 轉導回來的 `state` 是否跟先前發出的一致。 ### (6) call /auth2/token 取得 `ID Token` 透過 POST 方法發送至 token endpoint #### Header Basic access authentication : 通常為註冊的 `client_id` 與 註冊完 Authorization Server 發的 `client_secret` 之組合。`client_id`:`client_secret` 的 base64 編碼的 credentials,再放到 header 中,以下為 Basic access authentication 內容範例: ``` Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l ``` #### body 參數 | parameters | description | | ------ | ----------- | | code | 先前拿到的 Authorization Code| | grant_type | 採用到授權方式,授權碼授權方式基本上填 `authorization_code` | | redirect_uri | Authorization Server 的 callback 網址,必須跟步驟 (2) 一樣| ### (7) 回傳 ID Token & access token 驗證成功後 redirect_uri 會拿到 `ID Token` & `access token`,`ID Token` 帶有使用者資訊,`access token` 則是使用者授權使用某些服務或取得某些資訊的權限,換句話說,得到授權的 service 可以透過 `access token` 呼叫授權方的 API,使用某些服務或取得某些資訊。 token 很重要,要避免外洩,一般只會放在後端能存取的地方,應避免放在 cookie 或 LocalStorage 等等前端 user 可以查看的地方。 `ID Token` 是 JWT 格式,通常 payload 會紀錄使用者 ID、token 有效期限、發行單位、發行時間、OIDC Client ID、Authorization Server Login Session ID 等等資訊。 拿到 `ID Token` 之後要檢查一下下列資料: - JWT header 所標示的加密演算法是否符合 Authorization Server 所提供的演算法,EX: `RS256` - JWT payload 所標示的有效期限(`exp`)是否有過期 - JWT payload 所標示的 issuer (`iss`)是否為 Authorization Server - JWT payload 所標示的 cliend_id (`aud`)是不是 OIDC Client 自己的 client_id ## 驗證 ID Token 完整性 `ID Token` 是有可能被竄改的,因此可以用 Authorization Server 提供的 public key 進行完整性的驗證,才可信任 `ID Token` 所夾帶的資訊。 | 路徑 | 說明 | | ------ | ----------- | | GET /.well-known/jwks.json | 取得 Authorization Server 的 public key | 可以使用開源的 JWT 驗證工具進行驗證 ## 基本的登出 flow 登出有分兩種 flow ,[OpenID Connect Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html) 及 [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html),可以參考 [Hydra user-logout](https://www.ory.sh/docs/hydra/implementing-consent#user-logout) 說明,Front-Channel Logout 主要是 Authorization Server 對 User Agent 發出 302 轉址到 OIDC Client 註冊的 `frontchannel_logout_uri` ; Back-Channel Logout 主要是 Authorization Server 直接通知 OIDC Client 註冊的 `backchannel_logout_uri`,而下面內容為 Back-Channel Logout。 | 路徑 | 說明 | | ------ | ----------- | | GET /oauth2/sessions/logout | 登出 | ```sequence logoutFlow "User Agent"->"OIDC Client":登出 "OIDC Client"->"User Agent":(1) 回傳 state "User Agent"->"Authorization Server": (2) call /oauth2/sessions/logout "Authorization Server"->"Authorization Server": 登出流程 \n 註銷 id_token \n 清除 session or cookie 等等 "Authorization Server"->"OIDC Client": (3) call OIDC Client backchannel_logout_uri "OIDC Client"->"Authorization Server": (4) call /oauth2/revoke 註銷 Access token "Authorization Server"->"User Agent": (5) 回傳 302 & post_logout_redirect_uri 與 (6) 所需參數 "User Agent"->"OIDC Client":(6) redirect to post_logout_redirect_uri "OIDC Client"->"OIDC Client":OIDC Client 自身服務的登出作業 "OIDC Client"->"User Agent":登出成功 ``` **流程圖步驟 (3) 簡化只畫一條回 OIDC Client,其實還有其他的 OIDC Client 會被呼叫,這些 OIDC Clients 都會執行清除各自 `ID token` 的流程** ### (1) 回傳 state state 由 OIDC Client 隨機生成,之後步驟可與 Authorization Server 回傳的 state 比對,目的是為了確認轉導過程中的使用者都是同一個人,並且防止 CSRF 攻擊。 ### (2) call /oauth2/sessions/logout 透過 GET 方法發送至 Authorization Server logout endpoint GET 參數 | parameters | description | | ------ | ----------- | | id_token | 登入過程所拿到的的 `ID token`| | post_logout_redirect_uri | 必須跟 Authorization Server 註冊的一致 | | state | 為避免 CSRF 攻擊的一個隨機字串 | ### (3) call OIDC Client logout_uri Authorization Server 會進行一系列登出流程,並針對所有已登入的 OIDC Clients 發出 POST logout 請求,這些 uri 即為當初各 OIDC Clients 註冊的 backchannel_logout_uri。 Authorization Server 所發出 POST logout 帶有 `logout_token`,各個 OIDC Cliens 收到後要先驗證 `logout_token` 要檢查一下下列資料: - JWT header 所標示的加密演算法是否符合 Authorization Server 所提供的演算法,EX: `RS256` - JWT payload 所標示的 issuer (`iss`)是否為 Authorization Server。 - JWT payload 所標示的 cliend_id (`aud`)是不是 OIDC Client 自己的 client_id。 - 根據 JWT payload 所標示的 Authorization Server Login Session ID (`sid`),找出 OIDC Client 中對應的 `ID token` ( `ID token` 的 payload 也會有 `sid`),該 `ID token` 就是要被 OIDC Client 清除的。 ### (4) call /oauth2/revoke 註銷 Access token 透過 POST 方法發送至 revoke endpoint body 參數: | parameters | description | | ------ | ----------- | | token | 要註銷的 Access token | ### (5)(6) 回傳 state 到 logout_redirect_uri Authorization Server 會回傳一個 302 轉址到 `post_logout_redirect_uri` 的訊息給 User Agent,轉址內容含有 state 。 `post_logout_redirect_uri` 是有在 Authorization Server 註冊的 uri。 OIDC Client 的 `post_logout_redirect_uri` 執行自身的登出流程,EX: 清 cookie, session 等等,成功後即完成登出手續。 ## ID token 失效 當 OIDC Client 發現持有的 `ID token` 或過期,請走登出流程,讓使用者重新再登入取新的 `ID token` ## Broswer 安全性 即使近年來 Cross-Origin Resource Sharing (CORS) 被廣泛採用,不論是 OIDC 登入流程或 OAuth 身份授權,不建議由前端發起,前端取得的 `ID token` 或 `access token` 會被竊取。 最簡單的安全處理方式是由後端 server 發起 OIDC 登入流程或 OAuth 身份授權,把 `ID token` 及 `access token` 儲存在後端(放入 Session or 自行處理儲存方式),登入完成後由後端設定 `SameSite` 及 `HttpOnly` 的 Cookie(Session-ID or User-Info) 及 Signed Cookie。 前端每次發起請求,後端就驗證 Cookie & Signed Cookie,並找到相關 token 資料(可由 Session ID 找到 token 值或是自行設定的 Cookie 對應後端儲存資料內容),再帶著 `access token` 轉發到 Resource Server 或 Auth Server 要資料。 Cookie `SameSite` 設定可禁止跨網站發送該 Cookie,防止 CSRF 攻擊。 Cookie `HttpOnly` 設定可禁止 JavaScript 存取該 cookie,防止 XSS 攻擊。 Cookie 也可設定 `Secure` 屬性,傳輸時會進行加密。