# 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` 屬性,傳輸時會進行加密。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.