Skip to content

OAuth 2.0 學習筆記:理解 PKCE

Updated: at 12:00 PM

在研究 OAuth 2.0 的過程中,我發現 PKCE 這個機制特別有趣。這篇文章紀錄了我對 PKCE 的理解,以及它如何解決 Public Client 的安全性問題。

最近我開始深入研究 OAuth 2.0,這個在現代網路應用中被廣泛使用的授權框架。雖然網路上有許多關於 OAuth 2.0 的資源,但在閱讀的過程中我發現一個有趣的現象:OAuth 2.0 並不是一個單一的標準,而是由一系列相互關聯的規範所組成。這種分散的特性讓學習曲線變得相當陡峭,也讓我在設計系統時常常陷入選擇的困惑中。

正是這樣的困惑促使我開始寫這系列的學習筆記。一方面是為了梳理自己的思緒,另一方面也希望這些筆記能在未來需要時幫助我快速回憶重要的概念。今天,讓我們從 PKCE(發音為 “pixy”)開始談起。

什麼是 PKCE?

PKCE 的全名是 Proof Key for Code Exchange by OAuth Public Clients,定義在 RFC7636 中。這個名字雖然看起來很複雜,但其實相當貼切地描述了它的功能。要理解 PKCE,我們得先來看看它試圖解決的問題。

問題:Authorization Code 被攔截的風險

在常見的 Authorization Code Grant 流程中,client 需要用 Authorization Code 向 OAuth 2.0 provider 換取 Access Token。這個過程中存在一個安全風險:如果 Authorization Code 被惡意第三方攔截,他們可能搶先一步用這個 code 換取 Access Token。

Authorization Code Interception Attack 圖一: Authorization Code 被攔截的攻擊流程(擷取自RFC7636)。我們可以看到在第 4 步的地方,Authorization Code 被惡意的應用程式盜聽,並用他來跟 Authz Server 交換 Access Token。

經驗豐富的開發者可能會說:「等等,OAuth 2.0 不是有很多種 client 認證方式嗎?」沒錯,RFC6749 確實定義了 client_secret_basic、client_secret_post 等方法,RFC7523 也提供了 client_secret_jwt、private_key_jwt 的機制,RFC8705 更提供了 tls_client_auth 和 self_signed_tls_client_auth。

但這就是問題的關鍵:這些認證方式都不適用於 Public Client。

Public Client 的特殊性

根據 RFC6749 section 2.1 的定義,Public Client 指的是那些無法安全保存認證資訊的 Client,比如瀏覽器中的 JavaScript 應用程式或手機上的原生應用程式。這類應用程式面臨一個根本的問題:任何預先設定的認證資訊都可能被攻擊者從 JavaScript bundle 或 app binary 中取得。

PKCE 如何優雅地解決這個問題

Authorization Code 被攔截並被搶先交換 Access Token 這樣的脆弱性源自於 Authz Server 無法驗證 Access Token 的請求方究竟是 User 原本信任的 Client (Legitimate OAuth 2.0 App)還 是藉由盜聽取得 Authorization Code 的惡意程式(Malicious App)。

這個方法之所以有效,是因為在 OAuth 2.0 中,取得 Authorization Code 的請求必須通過安全的 TLS channel。這意味著我們可以確保這個請求確實來自使用者信任的 client。基於這個前提,PKCE 的運作流程是這樣的:

  1. Authorization Code Request (2)

    • 合法的 Client 生成一個隨機的密語(code verifier),例如 apple
    • 合法的 Client 使用 SHA-256 將密語轉換成 challenge,例如 3A7BD3E2360A3D29EEA436FCFB7E44C735D117C42D1C1835420B6B9942DD4F1B
    • 合法的 Client 將 challenge 和使用的演算法(code_challenge_method = "S256")透過安全的 TLS Channel 傳給 Authz Server
  2. Access Token Request (5)

    • Client為了證明自己是合法的 Client(證明自己是當初發出 (2)request 的一方),將原始密語(code verifier)與 Authorization Code 一併送給 Authz Server
    • Authz Server 對收到的密語進行 SHA-256 運算,比對結果是否與之前存的 challenge (hash 過的密語) 相符
    • 若相符則發放 Access Token,否則拒絕請求

關於 Plain 模式

RFC7636 還定義了一個較簡單的模式:code_challenge_method = plain。在這種模式下,client 直接傳送未經 hash 的密語。這種方式顯然無法抵禦網路監聽攻擊,因此只建議在無法進行 SHA-256 運算的極特殊情況下使用。

結語

在了解了 PKCE 的運作方式,我們再一次仔細看 PKCE 這個名字的全名:Proof Key for Code Exchange by OAuth Public Clients。裡面其實提到了好幾個重點。

  1. Proof Key: Proof Key 就是用來證明自己擁有 code verifier

  2. Code Exchange by OAuth Public Clients: 原始的 OAuth 2.0 在 public clients 進行 code exchange 的時候並無法分辨 Access Token 的請求方是否為惡意程式