驗證與授權的差別,淺談 OAuth 2.0 與 OpenID Connect

釐清 OAuth 2.0 與 OpenID Connect 的關係

驗證 - 確認使用者身份、授權 - 允許某人針對某些資源操作某些行為,兩者在對於自建網站可能是等同一件事,當我讓 user A 登入通過驗證,那也就等同授權 user A 可以操作他自己的資源

但今天如果驗證的服務、管理資源的服務、代替用戶操作資源的第三方服務都是獨立時,驗證與授權就有很大的區別,以下內容與截圖參考自

關於登入與 OAuth 2.0

最陽春的登入機制莫過於 client 提供 account / password 給 server,server 進去資料庫比對,成功則設定 cookie or session

這樣的技術有幾個缺點

  1. 安全性:如果用戶多個網站都用相同的帳密,則一個網站被攻破則每個網站都可能遭殃
  2. 維護性:每個網站都需要維護用戶的資料

第二個問題是如果某服務希望代替用戶操作另一個服務 (Delegated authorization),如果透過帳密非常危險,也沒有權限的限制,例如 Yelp 曾經跟用戶要帳密去登入 Email

OAuth 2.0 也就是在這樣的時空背景誕生

OAuth 2.0 簡單介紹

Authorization server 與 Resource server 有一套驗證與授權範圍管理的機制,Client app 取得 Resource Owner (user 本人) 的授權後,就可用 Access token 代為向 Resource server 發起操作

主要有四種流程

  1. Authorization Code flow
  2. implicit flow
  3. account / password flow
  4. client credential flow

以下僅介紹第一種

Authorization Code flow

基本流程為

  1. User 透過 Client App 指定要透過第三方登入
  2. 第三方 Auth Server 顯示授權頁面
  3. User 同意授權後,Auth Server 夾帶 code 轉導到 Client App
  4. Client App 透過 code + client 專屬資訊向 Auth Server 換 access token
  5. Client App 拿到 access token,就可以向 Resource Server 發起操作

特別注意到 channel 的概念,這邊分成

  • front channel
  • back channel

front channel 指的是在前端,因為這部分的流程暴露在客戶端,安全性相對較低,例如瀏覽器插件的側錄等; back channel 指的是後端,因為 server 是在我們掌控所以安全性相對高

所以任何東西從 front channel 轉到 back channel 都需要再次驗證,例如 front 回傳 code,此時 code 必須在 back channel 跟 Authorization server 換 access token

如果 front channel 直接回傳 access token,則有可能被替換的風險

OAuth 2.0 與 OpenID Connect

在一開始我們都會用 OAuth 2.0 驗證與授權一並處理,例如 Google OAuth 2.0 可以指定 Scope https://www.googleapis.com/auth/userinfo.profile ,但隨著驗證的需求越來越明確,包含手機驗證登入等,所以就又基於 OAuth 2.0 擴展推出了 OpenID Connect

OpenID Connect

  1. User 透過 Client App 指定要透過第三方登入
  2. 第三方 Auth Server 顯示授權頁面
  3. User 同意授權後,Auth Server 夾帶 id token 回到 Client App
  4. Client App 驗證 id token 後就可以拿到用戶基本資料
  5. 如有額外需要,Client App 可以用 id token 呼叫 Auth Server API (/userinfo 如 Google https://openidconnect.googleapis.com/v1/userinfo、 Line https://api.line.me/oauth2/v2.1/userinfo) 拿額外的用戶資訊

乍看之下 OpenID Connect 跟 OAuth 2.0 流程很像,但有個最巨大的差異

OpenID Connect 拿到的 id token 可以直接解析並讀取用戶資訊; 而 OAuth 2.0 拿到的 access token 並不是 Client App 要解讀,而是單純送給 Resource Server 驗證

所以從驗證角度,Server 透過 OpenID Connect 可以直接解析 id token,而不用多打一次 Api 去要用戶的資料

所以 OpenID Connect 有明文規定 token 必須是 jwt 格式,Client App 收到後要拿 Auth Server public key 檢查核發單位是否正確 (數位簽章的概念)
OAuth 2.0 用的 access token 不一定要是 jwt,任意的 string 都可以,只要 Auth Server 跟 Resource Server 認得就好

為什麼不拿 id token 當作 access token

  1. OAuth 2.0 有規範 sender contraint,限定 client 可以發送 access token,如果有實作那即使 token 被 hacker 偷走也沒關係,因為發送到 Resource server 會被擋下來
  2. Access Token 有更多的檢查,包含 scope (ID token 沒有) / Aud / token format validation

為什麼不拿 access token 當作 id token

  1. access token 不是要給 client 解析,而是給 resource server,client 最好把 access token 當作任意字串
  2. 並沒有統一標準取得用戶資訊

使用 JWT 的一些資安考量

因為 Token 是採用 JWT,在核發與驗證上要特別注意,否則會有資安上的漏洞

1. 務必檢查 iss / aud / exp 等基本訊息

RFC 7591 中的 payload 保留字段通常都是需要驗證的,包含

  • iss (issuer):誰核發的 token
  • aud (audience):誰應該收到此 token
  • sub (subject):token 核發的目的
  • exp (expiration):token 何時過期
  • nbf (Not Before):token 何時開始生效
  • iat (issued at):token 核發的時間
  • jti (JWT id):jwt ID,只有在可能發生碰撞才需要

2. 務必移除 alg: none 支援

JWT 的合法性需要透過 signature 去驗證,而 Hacker 可能指定 alg: “none” 跳過驗證,如果實作沒寫好就會真的被跳過; 看了一下 Ruby 實作沒有這個問題,會去檢查 alg 有沒有在指定的範圍內

1
2
3
def valid_alg_in_header?
    allowed_algorithms.any? { |alg| alg.valid_alg?(alg_in_header) }
end

3. 小心 alg 被調整

同樣是 alg 被調整,如果從非對稱加密被改成對稱加密,實作不恰當可能就會被繞過,記得要分清楚非對稱加密的 key 與對稱加密的 key

Single Sign On (SSO) 與 OAuth 2.0 / OpenID Connect 關係

我們常會看到 SSO 與 OAuth 2.0 / OIDC 出現,SSO 主要是描述一種概念,用戶只需要一組帳密登入驗證機構,多個服務都用此驗證機構驗明身份

authentication method that enables users to securely authenticate with multiple applications and websites by using just one set of credentials.

所以 OAuth 2.0 / OpenID Connect 都是 SSO 的一種實作,其他常見做法包含 SAML / LDAP 等

參考自 How Does Single Sign-On Work?

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus