跳至主要內容
Skip to content

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.0Client 是否被授權存取 APIAccess Token
OpenID ConnectClient 如何知道使用者身份ID Token

OAuth 2.0 問:

text
這個 Client 能不能代表使用者存取某個 API?

OIDC 問:

text
這個登入使用者是誰?

這是最常見面試陷阱。

延伸讀:OAuth 2.0 解決什麼問題。

2. OIDC 是 OAuth 2.0 的身份層 ​

OpenID Connect Core 1.0 規格定義了:OIDC 是建立在 OAuth 2.0 之上的 authentication layer,並使用 Claims 傳遞 End-User 資訊。

OIDC 增加:

  • openid scope。
  • ID Token。
  • UserInfo Endpoint。
  • Standard Claims。
  • Discovery。
  • JWKS。
  • nonce。

3. 第三方登入通常是 OIDC ​

當你看到:

text
使用 Google 登入
使用 Microsoft 登入
使用 Okta 登入

背後通常是 OIDC,而不是純 OAuth。

原因是 Client 不只需要 access token,它還需要可信的使用者身份資訊。


二、 ID Token 是什麼? ​

1. ID Token 是給 Client 的身份聲明 ​

ID Token 通常是 JWT,內容是 Authorization Server 對使用者身份的聲明。

範例:

json
{
  "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 ​

不要這樣:

http
Authorization: Bearer id_token

呼叫 API 應使用 access token。

ID Token 用途是:

text
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 TokenResource Server呼叫 API
ID TokenClient驗證使用者身份
Refresh TokenClient / 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 可以告訴你:

text
外部 provider 認證了這個 subject。

但你的系統仍要決定:

  • 是否建立本地 user。
  • 是否允許登入。
  • 是否綁定既有帳號。
  • 是否需要 email verified。
  • 是否需要 MFA。
  • 本地角色與權限。

OIDC 解決身份資訊,不解決你系統內所有授權問題。


四、 OIDC Flow ​

1. Authorization Code Flow + OIDC ​

OIDC 通常會在 OAuth authorization request 加上:

text
scope=openid profile email

只要有 openid scope,就表示這是 OIDC request。

2. UserInfo Endpoint ​

Client 可以用 access token 呼叫 UserInfo Endpoint:

http
GET /userinfo
Authorization: Bearer access_token

得到:

json
{
  "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:

text
/.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 裡最重要的使用者識別通常是:

text
iss + sub

不要只用 email 當唯一身份。

原因:

  • email 可能變更。
  • email 可能未驗證。
  • 不同 provider 可能有相同 email。
  • 企業租戶可能重用 email。

本地帳號綁定應存:

typescript
type ExternalIdentity = {
  provider: 'google' | 'microsoft' | 'line'
  issuer: string
  subject: string
  userId: string
}

2. Email 不一定可信 ​

OIDC 可能提供:

json
{
  "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 發起:

text
nonce=random-value

ID Token 回來後包含:

json
{
  "nonce": "random-value"
}

Client 驗證:

text
idToken.nonce === storedNonce

OpenID Connect Core 規格要求:如果 Authentication Request 包含 nonce,Authorization Server 必須在 ID Token 中包含相同 nonce;Client 也必須驗證。

2. Nonce、State、PKCE 差異 ​

機制解決問題
stateOAuth flow CSRF / client request binding
nonceID Token replay / authentication response binding
PKCEauthorization 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 應驗證:

text
取得 provider metadata
-> 取得 JWKS
-> 找到 kid 對應 public key
-> 驗證 signature
-> 驗證 iss
-> 驗證 aud
-> 驗證 exp / iat
-> 驗證 nonce,若使用
-> 驗證 sub 存在

簡化程式:

typescript
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:

json
{
  "aud": "client_123"
}

如果 aud 是別人的 client id,你不能接受。

3. Issuer 必須匹配 ​

要驗:

text
iss === expected issuer

不要只看 token 是某個 key 簽的。Issuer 是信任邊界。

4. 不要自己手刻驗證細節 ​

OIDC/JWT 驗證細節多,建議使用成熟 library。

但使用 library 也要正確設定:

  • issuer。
  • audience。
  • allowed algorithms。
  • clock tolerance。
  • nonce。
  • JWKS cache。

八、 第三方登入後的本地帳號 ​

1. 本地仍要建立 user ​

typescript
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
}

流程:

text
驗證 ID Token
-> 取得 iss + sub
-> 查 external identity
-> 找到就登入本地 user
-> 找不到就建立或進入綁定流程

2. 自動綁定要小心 ​

危險做法:

text
如果 Google email == 本地 email,就直接綁定。

要考慮:

  • 本地 email 是否已驗證。
  • 外部 email 是否 verified。
  • 是否同一租戶。
  • 是否需要使用者先登入本地帳號。
  • 是否有帳號接管風險。

帳號綁定會在後面獨立成篇。

3. 第三方登入後建立本地 session ​

OIDC 驗證成功後,通常建立你的系統 session:

text
OIDC login success
-> find or create local user
-> create local session / token
-> set local cookie

後續你的系統不應每次都依賴外部 ID Token 判斷本地授權。


九、 常見錯誤 ​

1. 拿 OAuth access token 當登入身份 ​

錯誤:

text
拿到 access token
-> decode
-> 當作 user login

Access token 是給 Resource Server 的,不一定是給 Client 的身份資料。

2. 不驗 ID Token signature ​

只 decode:

typescript
decode(idToken)

不安全。

3. 不驗 aud / iss ​

這可能接受別的 client 或別的 issuer 的 token。

4. 只用 email 當外部身份主鍵 ​

應使用:

text
issuer + subject

Email 可作為聯絡方式或輔助資料,不應是唯一外部身份主鍵。

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。
typescript
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 可以告訴你:

text
這個外部 subject 已被 provider 驗證。

但你的系統仍要決定:

  • 是否允許登入。
  • 是否需要綁定。
  • 是否需要本地 MFA。
  • 是否有本地權限。
  • 是否在正確 tenant。

2. Trust boundary 很重要 ​

你信任哪個 issuer?

text
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 之上加入 openid scope、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,並做本地權限、租戶、稽核與風險控制。

延伸閱讀 ​