上一篇把 PKCE 這個 Authorization Code Grant 的標準配備講完了,但到目前為止,這系列談的都還是 OAuth 2.0 的「授權(Authorization)」——讓 Client 拿到存取某個資源的權限。一個很現實的問題是:Client 真的知道操作它的這個人是誰嗎?這篇要接上的 OpenID Connect(OIDC),就是為了在 OAuth 2.0 之上,補上「身份驗證(Authentication)」這一塊。
今天內容涵蓋:
OAuth 2.0 專注在「授權」:讓 Client 在取得使用者同意後,可以拿著 Access Token 存取受保護的資源。也就是說,Access Token 解決的是「這個 Client 能不能存取某個 API」的問題;但它本身並不是設計來告訴 Client「目前操作的人是誰」。
這個差異在第三方登入情境中特別明顯。假設同一個 Client 同時支援 Google 與 GitHub 登入,Client 可以分別向 Google 和 GitHub 請求授權,取得 Access Token 後,再拿 Token 去呼叫各平台提供的使用者資料 API。問題是:OAuth 2.0 只規範授權流程,並沒有統一規範「使用者身份資料」應該用什麼欄位、什麼格式回傳。

以上圖來看,Client 做的事情其實很像:都是向不同 Provider 取得 Access Token,再用 Access Token 請求使用者資料。但各平台回傳的資料格式可能完全不同。以圖中的範例來說,Google 可能用 sub 表示使用者唯一識別碼、用 picture 表示頭像;GitHub 則可能用 id 表示使用者識別碼、用 avatar_url 表示頭像。
因此,在沒有 OIDC 的情況下,Client 如果要同時串接多個身份來源,就必須自行撰寫對應與轉換邏輯,把不同 Provider 回傳的資料整理成自己系統內部統一的使用者模型。例如把 Google 的 sub 和 GitHub 的 id 都轉成內部的 user_id,把 Google 的 picture 和 GitHub 的 avatar_url 都轉成內部的 avatar。
更重要的是,這樣做仍然沒有真正標準化「身份驗證」這件事。Client 拿到 Access Token,只能知道某個授權流程完成了,並且這個 Token 可以用來存取某些資源;但如果要正式判斷「目前登入的使用者是誰」,就需要一個標準化、可驗證、能明確表達使用者身份的機制。
⚠️常見的誤用是把 OAuth 2.0 直接當成登入機制。Access Token 的用途是讓 Client 存取受保護資源,而不是讓 Client 判斷使用者身份。OAuth 2.0 回答的是「能不能存取資料」,不是「誰正在使用系統」。
OpenID Connect(OIDC)就是為了解決這個問題而出現。它建立在 OAuth 2.0 之上,補上標準化的身份驗證層,讓 Client 可以透過 ID Token 與標準化的 Claims,確認目前登入的使用者是誰。
OIDC 可以簡單理解成一個公式:
OIDC = OAuth 2.0 + ID Token + UserInfo Endpoint
它在 OAuth 2.0 的基礎上,最核心是補上兩個東西:
此外,OIDC 也在 OAuth 2.0 既有的 Scope 機制上,定義了一組與身份資訊相關的標準 Scope,例如 openid、profile、email、address、phone。其中 openid 是 OIDC 認證請求的必要 Scope,用來明確表示這不是單純的 OAuth 2.0 授權請求,而是希望 Authorization Server 依照 OIDC 的規則處理身份驗證,並回傳與使用者身份相關的結果。其他 Scope 則用來描述 Client 希望取得哪些類型的使用者身份資訊。
💡一個常見的比喻:OAuth 2.0 像飯店的房卡——房卡能開門,但卡片本身不記名,門鎖只在乎這張卡有沒有權限。OIDC 則像是飯店在給房卡的同時,額外核發一張身分證(ID Token)——上面明確寫著你的名字,還由飯店(Authorization Server/OpenID Provider)掛保證。
OIDC 是建立在 OAuth 2.0 之上,許多角色與名詞都延續自 OAuth 2.0;不過因為 OIDC 多了「認證(Authentication)」這一層,所以有些名稱在 OIDC 情境下會換成更貼近身份驗證的說法:
| OAuth 2.0 | OpenID Connect(OIDC) |
|---|---|
| 資源擁有者(Resource Owner) | 終端使用者(End User) |
| 客戶端(Client) | 依賴方(Relying Party, RP) |
| 授權伺服器(Authorization Server) | OpenID 提供者(OpenID Provider, OP) |
| 授權請求(Authorization Request) | 認證請求(Authentication Request) |
| Scope | OIDC 標準 Scope,例如 openid、profile、email |
| Access Token | Access Token + ID Token |
其中最關鍵的是 OpenID 提供者(OP):它不只是 OAuth 2.0 的 Authorization Server,也會把「使用者的身份資訊」當作受保護資源來處理,所以回應中除了 Access Token,還多了 ID Token。
一個較完整的 OIDC 登入流程,通常會把三件事串在一起看:使用者登入、Relying Party 驗證 ID Token,以及後端建立自己的 Session。

以上圖為例,可以直接依照圖上的編號閱讀:
這裡先抓住一個重點:**ID Token 用來確認使用者身份;Access Token 用來呼叫受保護 API。**在這張圖的流程中,Backend 驗證 ID Token 後才建立應用程式自己的 Session;Access Token 則會在需要更多使用者資訊時,用來呼叫 UserInfo Endpoint。
💡如果 Relying Party 是第三方應用,OpenID Provider 通常會在登入後顯示同意畫面,讓使用者確認是否允許該應用取得指定範圍的資料。這個同意步驟屬於 OAuth 2.0 原本的授權邏輯;OIDC 補上的 ID Token,則負責把使用者身份用標準格式交給 Relying Party 驗證。
ID Token 是一個 JWT,因此也可以拆成 Header、Payload、Signature 三個部分。這裡先看中間的 Payload,也就是 Token 裡實際承載的資料內容;在 ID Token 裡,Payload 主要放的是使用者身份相關的 Claims:
{
"iss": "https://server.example.com",
"sub": "24400320",
"aud": "s6BhdRkqt3",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"nonce": "n-0S6_Wox69"
}
| Claim | 說明 |
|---|---|
iss |
發行者(Issuer),也就是 OP 的識別碼 |
sub |
使用者的唯一識別碼(Subject),不會隨 email 等資訊變動 |
aud |
受眾(Audience),應該要等於發出請求的 Client ID |
exp / iat |
過期時間與簽發時間 |
nonce |
Client 發起請求時附上的隨機值,原封不動地被放回 ID Token,用來防止重放攻擊 |
Relying Party 收到 ID Token 後,不能只因為「有拿到 Token」就直接判定使用者登入成功,還必須先確認這個 ID Token 是有效且可信任的:
iss:確認發行者是不是預期中的 OP。aud:確認這個 Token 是發給自己(而不是別的 Client)。exp:確認 Token 還沒過期。nonce:比對是否跟自己當初發送的值一致,避免這是一個被攔截後重複使用的舊 Token。⚠️常見的錯誤是將 Access Token 視為使用者身份的證明。Access Token 的主要用途是讓 Resource Server 判斷請求是否具備存取特定資源的權限;它不一定包含完整、穩定且可供 Client 判斷使用者身份的資訊。因此,Client 若要確認目前登入的使用者身份,應以經過驗證的 ID Token 為依據,而不是直接依賴 Access Token。
UserInfo Endpoint 可以理解成 OpenID Provider 提供的標準查詢端點。當 Relying Party 需要取得目前登入使用者的補充資料,例如姓名、Email、生日或地址,就可以帶著 Access Token 呼叫這個端點。也就是說,ID Token 主要用來確認「使用者是誰」;UserInfo Endpoint 則用來補充取得「這個使用者有哪些資料」。
請求方式如下:
GET /userinfo HTTP/1.1
Host: openid-provider.example.com
Authorization: Bearer some-access-token
OP 驗證 Access Token 有效後,會回傳使用者資料:
{
"sub": "user123",
"name": "Gloria",
"email": "gloria@foo.com",
"birthdate": "2001-05-25",
"address": {
"street": "信義路五段 7 號",
"city": "Taipei",
"country": "TW"
}
}
UserInfo Endpoint 回傳的欄位,會依照 Relying Party 在認證請求中要求的 Scope,以及使用者實際同意的範圍而有所不同。因此,Relying Party 不應一次要求過多資料,而是應遵守最小權限原則,只請求真正需要的使用者資訊。
一般情況下,UserInfo Endpoint 會回傳 JSON 格式的使用者資料;有些 OpenID Provider 為了提高資料完整性或保密性,也可能把回應包成 JWT。這種做法可以讓 Relying Party 驗證回應是否由可信任的 OpenID Provider 產生,或避免使用者資料在傳遞過程中被直接讀取。
每個 OP 需要曝露的端點(授權、Token、UserInfo、簽署金鑰...)都不太一樣,如果每次串接都要手動查文件填 URL,很容易出錯。OIDC Discovery 就是讓 Client 能自動找到這些設定的機制。
只要知道 OP 的 issuer URL,就可以在它後面加上固定路徑 /.well-known/openid-configuration,取得一份 JSON 格式的設定文件,例如:
{
"issuer": "https://oidc.example.com",
"authorization_endpoint": "https://oidc.example.com/authorize",
"token_endpoint": "https://oidc.example.com/token",
"userinfo_endpoint": "https://oidc.example.com/userinfo",
"jwks_uri": "https://oidc.example.com/.well-known/jwks.json"
}
| 欄位 | 說明 |
|---|---|
issuer |
OP 的唯一識別碼,也用來驗證 Token 的 iss |
authorization_endpoint |
發起認證請求的網址 |
token_endpoint |
交換 Token 的網址 |
userinfo_endpoint |
UserInfo Endpoint 的網址 |
jwks_uri |
取得公開簽署金鑰、驗證 Token 簽章用的網址 |
驗證程式庫通常會直接讀這份設定文件,開發者很少需要手動組出這些請求。如果連 issuer 的位置都還不知道,OIDC 也定義了透過 WebFinger(用 email 或 URL 查詢)來反查 issuer 的方式,但實務上更常見的是 OP 直接把 issuer URL 寫在文件裡。
OIDC 的流程設計其實不是從零開始,而是延續 OAuth 2.0 既有的流程,再加入身份驗證需要的內容。也就是說,前面在 OAuth 2.0 看到的 Grant Type 概念,到了 OIDC 仍然存在;只是 OIDC 會在原本的授權流程上,加入 openid Scope、ID Token,以及身份資訊相關的 Claims,讓 Relying Party 不只取得存取資源的權限,也能確認目前登入的使用者身份。
常見的 OIDC 流程可以分成三種:
openid Scope,因此 Token Endpoint 回傳的結果除了 Access Token 之外,還會多一個 ID Token,讓 Relying Party 驗證使用者身份。因此,這三種流程可以先用同一個角度理解:它們都延續 OAuth 2.0 的流程概念,只是 OIDC 會在流程中加入 ID Token。
OAuth 2.0 本身主要解決的是「授權」問題;OIDC 則是在 OAuth 2.0 之上補上「身份驗證」的一層。它最重要的新增內容是 ID Token,讓 Relying Party 可以用標準化 Claims 驗證目前登入的使用者身份。
整理 OIDC 的重點:
下一篇會回到 OAuth 2.0 的授權流程,討論一個完全沒有使用者參與的場景:當後端服務、自動化程式或內部系統需要用自己的身份呼叫 API 時,Client Credentials Grant 是怎麼讓 Client 取得 Access Token 的。