OpenID Connect 與 OAuth 的差異
OAuth 2.0 常被拿來做「第三方登入」,但嚴格來說,OAuth 2.0 本身是授權框架,不是身份驗證協議。真正補上登入身份層的是 OpenID Connect,也就是 OIDC。
一句話回答:OAuth 2.0 解決的是授權:Client 如何取得 Access Token 去存取 Resource Server;OpenID Connect 則是在 OAuth 2.0 之上加入身份驗證層,透過 openid scope、ID Token、UserInfo Endpoint 與標準 Claims,讓 Client 可以驗證 End-User 的身份。OAuth 的 Access Token 是給 API 用的,OIDC 的 ID Token 是給 Client 驗證登入身份用的,兩者不能混用。
一、 必考觀念
1. OAuth 授權,OIDC 登入
| 協議 | 解決問題 | 主要 token |
|---|---|---|
| OAuth 2.0 | Client 是否被授權存取 API | Access Token |
| OpenID Connect | Client 如何知道使用者身份 | ID Token |
OAuth 2.0 問:
這個 Client 能不能代表使用者存取某個 API?OIDC 問:
這個登入使用者是誰?這是最常見面試陷阱。
延伸讀:OAuth 2.0 解決什麼問題。
2. OIDC 是 OAuth 2.0 的身份層
OpenID Connect Core 1.0 規格定義了:OIDC 是建立在 OAuth 2.0 之上的 authentication layer,並使用 Claims 傳遞 End-User 資訊。
OIDC 增加:
openidscope。- ID Token。
- UserInfo Endpoint。
- Standard Claims。
- Discovery。
- JWKS。
- nonce。
3. 第三方登入通常是 OIDC
當你看到:
使用 Google 登入
使用 Microsoft 登入
使用 Okta 登入背後通常是 OIDC,而不是純 OAuth。
原因是 Client 不只需要 access token,它還需要可信的使用者身份資訊。
二、 ID Token 是什麼?
1. ID Token 是給 Client 的身份聲明
ID Token 通常是 JWT,內容是 Authorization Server 對使用者身份的聲明。
範例:
{
"iss": "https://accounts.example.com",
"sub": "248289761001",
"aud": "client_123",
"exp": 1716533600,
"iat": 1716530000,
"nonce": "n-0S6_WzA2Mj",
"email": "user@example.com",
"email_verified": true,
"name": "Hirimu"
}ID Token 的接收方是 Client。
Access Token 的接收方通常是 Resource Server。
2. ID Token 不是拿來呼叫 API
不要這樣:
Authorization: Bearer id_token呼叫 API 應使用 access token。
ID Token 用途是:
Client 驗證使用者身份
建立本地登入狀態
綁定本地帳號3. ID Token Payload 可讀,不等於可亂信
ID Token 常是 JWT,但 Client 必須驗證:
- signature。
- issuer。
- audience。
- expiration。
- issued at。
- nonce,若有使用。
- authorized party,視情況。
- key id / JWKS。
只 decode ID Token 是不夠的。
延伸讀:JWT 是什麼。
三、 Access Token、ID Token、Refresh Token
1. 三者用途不同
| Token | 給誰用 | 用途 |
|---|---|---|
| Access Token | Resource Server | 呼叫 API |
| ID Token | Client | 驗證使用者身份 |
| Refresh Token | Client / Auth Server | 換新的 token |
不要混用。
2. Access Token 不一定能被 Client 解讀
有些 provider 的 access token 是 JWT。
有些是 opaque token。
即使是 JWT,Client 也不應假設 access token 的 claims 可作為登入身份。
因為:
- audience 可能是 API,不是 Client。
- claims 格式由 Resource Server 使用。
- access token 可能不包含身份資訊。
- access token 的語意是授權,不是登入身份。
3. ID Token 不代表本地授權完成
ID Token 可以告訴你:
外部 provider 認證了這個 subject。但你的系統仍要決定:
- 是否建立本地 user。
- 是否允許登入。
- 是否綁定既有帳號。
- 是否需要 email verified。
- 是否需要 MFA。
- 本地角色與權限。
OIDC 解決身份資訊,不解決你系統內所有授權問題。
四、 OIDC Flow
1. Authorization Code Flow + OIDC
OIDC 通常會在 OAuth authorization request 加上:
scope=openid profile email只要有 openid scope,就表示這是 OIDC request。
2. UserInfo Endpoint
Client 可以用 access token 呼叫 UserInfo Endpoint:
GET /userinfo
Authorization: Bearer access_token得到:
{
"sub": "248289761001",
"email": "user@example.com",
"email_verified": true,
"name": "Hirimu"
}注意:
- UserInfo 回來的
sub應和 ID Token 的sub一致。 - UserInfo 資料仍要依 provider 信任模型處理。
3. Discovery 與 JWKS
OIDC Provider 通常提供 discovery document:
/.well-known/openid-configuration裡面包含:
- authorization_endpoint。
- token_endpoint。
- userinfo_endpoint。
- jwks_uri。
- issuer。
- supported scopes。
- supported signing algorithms。
Client 可用 jwks_uri 取得 public keys 驗證 ID Token。
五、 Claims 與 Subject
1. sub 是使用者在 Provider 的穩定識別
OIDC 裡最重要的使用者識別通常是:
iss + sub不要只用 email 當唯一身份。
原因:
- email 可能變更。
- email 可能未驗證。
- 不同 provider 可能有相同 email。
- 企業租戶可能重用 email。
本地帳號綁定應存:
type ExternalIdentity = {
provider: 'google' | 'microsoft' | 'line'
issuer: string
subject: string
userId: string
}2. Email 不一定可信
OIDC 可能提供:
{
"email": "user@example.com",
"email_verified": true
}要注意:
- 是否有
email_verified。 - Provider 是否可信。
- email 是否已被本地帳號使用。
- 是否允許自動綁定。
對高風險系統,不應只靠 email 相同就自動合併帳號。
3. Standard Claims
OIDC 定義了許多 standard claims,例如:
- name。
- given_name。
- family_name。
- email。
- email_verified。
- picture。
- locale。
這些通常用於 profile,不應直接當作權限。
六、 Nonce
1. Nonce 解決什麼?
nonce 用來把 authentication request 和 ID Token 綁在一起,防止 replay。
Client 發起:
nonce=random-valueID Token 回來後包含:
{
"nonce": "random-value"
}Client 驗證:
idToken.nonce === storedNonceOpenID Connect Core 規格要求:如果 Authentication Request 包含 nonce,Authorization Server 必須在 ID Token 中包含相同 nonce;Client 也必須驗證。
2. Nonce、State、PKCE 差異
| 機制 | 解決問題 |
|---|---|
| state | OAuth flow CSRF / client request binding |
| nonce | ID Token replay / authentication response binding |
| PKCE | authorization code interception |
三者不是互相替代。
3. Authorization Code Flow 一定要 nonce 嗎?
不同 profile 與 provider 實作可能有細節差異。
保守做法:
- 使用 state。
- 使用 PKCE。
- 若發送 nonce,就驗證 ID Token nonce。
- 對 hybrid / implicit flow 更要注意 nonce。
現代 SPA / Web 最推薦仍是 Authorization Code + PKCE。
七、 驗證 ID Token
1. 驗證步驟
Client 應驗證:
取得 provider metadata
-> 取得 JWKS
-> 找到 kid 對應 public key
-> 驗證 signature
-> 驗證 iss
-> 驗證 aud
-> 驗證 exp / iat
-> 驗證 nonce,若使用
-> 驗證 sub 存在簡化程式:
const claims = await oidcVerifier.verifyIdToken(idToken, {
issuer: 'https://accounts.example.com',
audience: 'client_123',
nonce: storedNonce,
})2. Audience 必須是你的 client_id
ID Token 的 aud 應該包含你的 client id:
{
"aud": "client_123"
}如果 aud 是別人的 client id,你不能接受。
3. Issuer 必須匹配
要驗:
iss === expected issuer不要只看 token 是某個 key 簽的。Issuer 是信任邊界。
4. 不要自己手刻驗證細節
OIDC/JWT 驗證細節多,建議使用成熟 library。
但使用 library 也要正確設定:
- issuer。
- audience。
- allowed algorithms。
- clock tolerance。
- nonce。
- JWKS cache。
八、 第三方登入後的本地帳號
1. 本地仍要建立 user
type User = {
id: string
email?: string
displayName?: string
status: 'active' | 'disabled'
}
type ExternalIdentity = {
id: string
userId: string
provider: string
issuer: string
subject: string
email?: string
emailVerified?: boolean
}流程:
驗證 ID Token
-> 取得 iss + sub
-> 查 external identity
-> 找到就登入本地 user
-> 找不到就建立或進入綁定流程2. 自動綁定要小心
危險做法:
如果 Google email == 本地 email,就直接綁定。要考慮:
- 本地 email 是否已驗證。
- 外部 email 是否 verified。
- 是否同一租戶。
- 是否需要使用者先登入本地帳號。
- 是否有帳號接管風險。
帳號綁定會在後面獨立成篇。
3. 第三方登入後建立本地 session
OIDC 驗證成功後,通常建立你的系統 session:
OIDC login success
-> find or create local user
-> create local session / token
-> set local cookie後續你的系統不應每次都依賴外部 ID Token 判斷本地授權。
九、 常見錯誤
1. 拿 OAuth access token 當登入身份
錯誤:
拿到 access token
-> decode
-> 當作 user loginAccess token 是給 Resource Server 的,不一定是給 Client 的身份資料。
2. 不驗 ID Token signature
只 decode:
decode(idToken)不安全。
3. 不驗 aud / iss
這可能接受別的 client 或別的 issuer 的 token。
4. 只用 email 當外部身份主鍵
應使用:
issuer + subjectEmail 可作為聯絡方式或輔助資料,不應是唯一外部身份主鍵。
5. 把 ID Token 長期當 session
ID Token 是外部身份聲明。
你的系統應建立自己的 session,管理自己的登出、權限、稽核與風險狀態。
十、 追問題庫
Q1:OIDC 和 OAuth 差在哪?
OAuth 2.0 是授權框架,用 Access Token 讓 Client 存取 Resource Server;OIDC 是 OAuth 2.0 上的身份層,用 ID Token 和 UserInfo 讓 Client 驗證使用者身份。
Q2:ID Token 是什麼?
ID Token 是 OpenID Provider 給 Client 的身份聲明,通常是 JWT,包含 iss、sub、aud、exp、iat、nonce 以及使用者 claims。
Q3:Access Token 和 ID Token 差在哪?
Access Token 給 Resource Server,用來呼叫 API;ID Token 給 Client,用來驗證使用者身份。ID Token 不應拿去呼叫 API。
Q4:為什麼不能只用 email 當使用者識別?
Email 可能變更、未驗證、不同 provider 可能重複。OIDC 應使用 issuer + subject 作為外部身份穩定識別。
Q5:nonce 有什麼用?
Nonce 用來把 authentication request 和 ID Token 綁定,防止 ID Token replay。若請求帶 nonce,回來的 ID Token 要包含相同 nonce,Client 必須驗證。
Q6:UserInfo Endpoint 有什麼用?
Client 可以用 access token 呼叫 UserInfo Endpoint 取得使用者 profile claims,例如 email、name、picture。UserInfo 的 sub 應與 ID Token 的 sub 一致。
十一、 實作題
題目:設計 OIDC Login Callback
需求:
- 使用 Authorization Code + PKCE。
- 驗證 state。
- 交換 token。
- 驗證 ID Token。
- 建立或找到本地 user。
- 建立本地 session。
async function handleOidcCallback(query: CallbackQuery) {
const saved = oidcStateStore.get(query.state)
if (!saved) {
throw new InvalidStateError()
}
const tokenSet = await oidcClient.exchangeCode({
code: query.code,
redirectUri: 'https://app.example.com/auth/callback',
codeVerifier: saved.codeVerifier,
})
const claims = await oidcClient.verifyIdToken(tokenSet.id_token, {
issuer: 'https://accounts.example.com',
audience: 'client_123',
nonce: saved.nonce,
})
const externalIdentity = {
provider: 'example',
issuer: claims.iss,
subject: claims.sub,
}
const user = await userService.findOrCreateByExternalIdentity({
externalIdentity,
email: claims.email,
emailVerified: claims.email_verified,
displayName: claims.name,
})
const session = await sessionService.create({
userId: user.id,
loginMethod: 'oidc',
})
return {
session,
user,
}
}重點:
- state 必須驗。
- code_verifier 必須用。
- ID Token 必須驗簽與 claims。
- 本地 user / session 仍由你的系統建立。
十二、 資深視角
1. OIDC 是身份交換,不是權限外包
OIDC provider 可以告訴你:
這個外部 subject 已被 provider 驗證。但你的系統仍要決定:
- 是否允許登入。
- 是否需要綁定。
- 是否需要本地 MFA。
- 是否有本地權限。
- 是否在正確 tenant。
2. Trust boundary 很重要
你信任哪個 issuer?
https://accounts.google.com
https://login.microsoftonline.com/{tenant}
https://your-company.okta.com不同 issuer 的 subject 不能混在一起。
3. 企業登入要處理 tenant
企業 OIDC 常有:
- organization。
- tenant id。
- domain restriction。
- group claims。
- SCIM provisioning。
- IdP initiated login。
這已經接近 SSO 系統設計,會在後面的 SSO / SAML / OIDC 文章展開。
總結
| 問題 | 回答重點 |
|---|---|
| OAuth | 授權 API 存取 |
| OIDC | 在 OAuth 2.0 上加入身份登入層 |
| ID Token | 給 Client 驗證使用者身份 |
| Access Token | 給 Resource Server 呼叫 API |
| UserInfo | 取得使用者 claims |
| subject | 使用者在 issuer 下的穩定識別 |
| nonce | 防 ID Token replay |
| 本地帳號 | OIDC 成功後仍要建立本地 user/session |
一句完整的面試回答可以是:
OAuth 2.0 和 OIDC 的差異在於 OAuth 解決授權,OIDC 解決登入身份。OAuth 2.0 讓 Client 透過 access token 存取 Resource Server 上的 API;OpenID Connect 在 OAuth 2.0 之上加入
openidscope、ID Token、UserInfo Endpoint 和標準 claims,讓 Client 可以驗證 End-User 是誰。Access token 是給 Resource Server 的,不應拿來當登入身份;ID Token 是給 Client 的身份聲明,通常是 JWT,必須驗簽章、issuer、audience、expiration、nonce 和 subject。使用者的外部身份應以 issuer + subject 綁定本地 user,而不是只用 email。OIDC 登入成功後,系統仍要建立自己的 local session,並做本地權限、租戶、稽核與風險控制。