上一篇把 Authorization Code Grant 的完整流程走了一遍,也提到 SPA、行動 App 這類 Public Client 無法安全保存 client_secret,光靠傳統 Authorization Code Grant 還不夠安全。根據 2025 年發布的 RFC 9700(OAuth 2.0 Security Best Current Practice),所有使用 Authorization Code Grant 的 Client 都必須搭配 PKCE(Proof Key for Code Exchange)。這篇就把 PKCE 到底在防什麼、怎麼運作講清楚。
今天內容涉蓋:
RFC 7636 開頭就講了一個具體的攻擊情境:行動 App 沒有像 Web 後端那樣,有一個外界進不去的網域可以當作 redirect_uri,只能用作業系統層級的「自訂 URI Scheme」(例如 myapp://callback)來接收 Authorization Server 導回的 Authorization Code。
問題在於,作業系統通常允許多個 App 註冊同一個自訂 URI Scheme。如果攻擊者寫一個惡意 App,也註冊了同樣的 Scheme,就有機會比合法 App 先攝取到這個 Authorization Code。拿到 Code 之後:
⚠️ 這個攻擊不需要破解 TLS,重點在於「作業系統層面」的 redirect 這一段——Authorization Server 到 App 之間如果是走自訂 URI Scheme,就有被其他 App 冒名接收的風險,跟 Authorization Server 本身安不安全無關。
PKCE 的做法,是讓「發起授權請求的人」跟「拿 Code 換 Token 的人」必須證明是同一個 Client,具體透過幾個新參數:
| 參數 | 說明 |
|---|---|
| code_verifier | Client 自己產生的一段隨機字串,長度 43~128 碼,必須足夠隨機、難以猜測,只有 Client 自己知道 |
| code_challenge | 把 code_verifier 用雜湊函式(建議用 SHA-256)算出來的結果,會被送到 Authorization Server |
| code_challenge_method | 說明 code_challenge 是用什麼方法算出來的,通常是 S256(雜湊)或 plain(不雜湊) |
實際流程比 Authorization Code Grant 多了這幾個動作:

流程可以拆成下面幾步:
code_verifier,並根據它計算出 code_challenge。code_challenge。code_verifier。code_verifier 重新計算、比對一開始保存的 code_challenge。💡和上一篇 Day 09 的 Authorization Code Grant 流程圖對照來看,PKCE 不是重新發明一套全新的授權流程,而是在原本「送出授權請求」和「用 Authorization Code 換 Token」這兩個位置,額外加入
code_challenge與code_verifier,用來確認前後是同一個 Client 發起的流程。
回到第一節的攻擊情境,攻擊者真正拿到的通常只有「導回 Client 這一段」出現的 Authorization Code。這段 Code 本身只是暫時的中繼憑證,還不能直接拿來存取 Resource Server;它必須再到 Token 端點交換成 Access Token 才有用。
PKCE 的關鍵就在這裡:Authorization Code 雖然可能被攔截,但 code_verifier 從頭到尾沒有出現在前面的導向流程中。它只保存在原本發起流程的 Client 端,並且只會在最後向 Token 端點交換 Token 時送出。
也就是說,即使攻擊者攝取到 Authorization Code,也沒有 code_verifier,就沒辦法通過 Authorization Server 的比對,Token 端點會直接拒絕發放 Token。
💡這也是為什麼 code_challenge_method 建議用 S256 而不是 plain。用 plain 的話,code_challenge 就等於 code_verifier 本身,如果攻擊者連 Authorization Request 這一段(帶 code_challenge)也攝取得到,就等於直接拿到了 code_verifier,PKCE 的保護就失效了。
PKCE 常見的誤解,是把它當成「client_secret 的替代品」——因為它最早就是為了 Public Client(沒有 client_secret 的 SPA、行動 App)設計的。但這個理解不完全正確:
PKCE 最初(RFC 7636,2015 年發布)是設計給 Public Client 用的強化機制,但這幾年的走向是「所有 Client 都該用」:
| 階段 | 狀態 |
|---|---|
| RFC 7636(2015) | 定義 PKCE,主要動機是保護 Public Client(行動 App) |
| 後續業界共識 | 建議所有 Authorization Code Grant,不論 Public 或 Confidential Client,都加上 PKCE |
| RFC 9700(2025) | 用 MUST 等級的規範用詞規定所有 Client 都要搭配 PKCE,並明証這個建議適用於所有類型的 OAuth Client,不只是原本設計的目標——原生 App |
| OAuth 2.1(草案) | 直接把 PKCE 列為 Authorization Code Grant 的強制要求,適用所有 Client 類型 |
💡這代表 PKCE 已經從「Public Client 專屬的補丁」,變成 Authorization Code Grant 的標準配備——之後只要看到 Authorization Code Grant,預設就該假設它會搭配 PKCE 一起實作。
PKCE 解決的是 Authorization Code Grant 裡一個關鍵風險:如果 Authorization Code 在導回 Client 的過程中被攔截,攻擊者可能會拿這組 Code 去 Token Endpoint 換取 Access Token。
它的做法,是讓 Client 在一開始發起授權請求時先準備一組秘密值,並把由 code_verifier 推導出的 code_challenge 送給 Authorization Server。等到後面要用 Authorization Code 換 Token 時,再送出原本的 code_verifier,由 Authorization Server 比對兩者是否相符。
可以整理 PKCE 的重點:
code_verifier 是關鍵秘密:攻擊者就算拿到 Authorization Code,如果沒有最初那組 code_verifier,也無法成功換到 Access Token。下一篇會接上 OpenID Connect(OIDC)。前面幾篇主要都在講 OAuth 2.0 如何處理「授權」;OIDC 則是在 OAuth 2.0 之上補上「身份驗證」,讓 Client 不只知道自己能不能存取資源,也能確認目前登入的使用者是誰。