Pull to refresh
medical Public fhir smart-on-fhir oauth2 pkce tls authorization-code security

FHIR 用 OAuth2 + PKCE 交換資料的運作機制:傳輸加密與授權碼防護是兩件事

拆解 FHIR(SMART on FHIR)如何用 OAuth2 授權碼流程交換臨床資料,釐清常見誤解——PKCE 不是加密資料,而是防止授權碼被攔截盜用;資料加密靠 TLS。

| Ingested 2026-06-13 |

先講結論,避免一個常見誤解:
「FHIR 用 PKCE 加密資料在網路上傳輸」——這句話把兩件事混在一起了。
- 資料在網路上的加密 → 靠 TLS(HTTPS),跟 OAuth2 / PKCE 無關。
- PKCE → 不加密任何病歷資料,它保護的是 OAuth2 流程中的授權碼(authorization code)
防止這張「換 Token 的票根」在重導向過程被攔截、被別人拿去冒領 Access Token。

換句話說:TLS 管「資料在路上不被偷看」,PKCE 管「換 Token 的票根不被冒用」,OAuth2 Scope 管「誰能讀哪些資源」。 三層各司其職。


三層防護,各管一件事

FHIR 是一套 REST API(GET /Patient/123GET /Observation?patient=123...),
本身只是「資源伺服器」。要安全地對外交換臨床資料,SMART on FHIR 把 OAuth2 / OIDC
套在它前面。整條鏈路上有三層獨立的保護:

機制 保護什麼 失效後果
傳輸加密 TLS 1.2+(HTTPS) 封包內容在網路上不被竊聽 / 竄改 資料在路上被明文側錄
授權碼防護 PKCE 授權碼不被攔截後冒領 Token 攻擊者用偷來的 code 換到 Token
存取授權 OAuth2 Access Token + Scope 限定「誰」能讀「哪些」FHIR 資源 越權讀取任意病患資料

下面把重點放在使用者問的兩件事:資料在網路上怎麼加密,以及 PKCE 的運作機制


第一件事:資料在網路上的加密 = TLS

FHIR 規範明確要求所有交換走 HTTPS(TLS)。這一層做的事:

  • 加密:Access Token、病歷 JSON、查詢參數,在網路上全程是密文。
  • 完整性:封包被中途竄改會被偵測。
  • 伺服器身分驗證:憑證確保你連到的是真正的醫院 FHIR 伺服器,不是釣魚站。

院內對外時常再加一層 mTLS(雙向 TLS),讓 FHIR Box 與院內 Proxy 互相驗證憑證
(見 FHIR Box PHI 安全實務指南)。

這一層跟 OAuth2 / PKCE 完全獨立。 就算沒有 OAuth2,HTTPS 一樣會把資料加密;
反過來,OAuth2 / PKCE 也不負責加密病歷內容。


第二件事:PKCE 的運作機制

PKCE 全名 Proof Key for Code Exchange(RFC 7636),讀作「pixy」。
它是 OAuth2 授權碼流程(Authorization Code Flow) 的加強,解決一個具體攻擊:
授權碼攔截攻擊(Authorization Code Interception)。

為什麼需要它

OAuth2 授權碼流程分兩步拿 Token:

  1. App 把使用者導到授權伺服器登入、同意 → 授權伺服器把一張授權碼重導回 App。
  2. App 拿這張授權碼,去跟授權伺服器換 Access Token

問題在第 1 步的重導回。手機 App 常用 custom URI scheme(myapp://callback)接收授權碼,
同一支手機上的惡意 App 可能搶註冊同一個 scheme,把授權碼攔下來。傳統做法靠
client_secret 證明身分,但手機 / 單頁式(SPA)醫療 App 是 public client,無法安全保存
secret
(反編譯就看得到)。於是授權碼一旦被攔,攻擊者就能直接換到 Token。

PKCE 用一組一次性、動態產生的密鑰取代固定的 client_secret,補上這個洞。

三個關鍵參數

參數 誰產生 怎麼算
code_verifier App 端隨機產生 43–128 字元的高熵亂數,全程留在 App 本機,不上網
code_challenge App 端 BASE64URL( SHA256( code_verifier ) )
code_challenge_method App 端 S256(即 SHA-256;不要用 plain

核心是單向雜湊的不可逆:知道 code_challenge(SHA-256 的結果)算不回
code_verifier

運作流程

sequenceDiagram
    participant App as 醫療 App<br/>(public client)
    participant AS as 授權伺服器<br/>(醫院 OAuth2/OIDC)
    participant FHIR as FHIR 資源伺服器

    Note over App: 1. 產生隨機 code_verifier<br/>計算 code_challenge = S256(verifier)
    App->>AS: 2. 授權請求<br/>帶 code_challenge + method=S256
    Note over AS: 暫存此次的 code_challenge
    AS-->>App: 3. 使用者登入+同意後<br/>重導回授權碼 code
    Note over App,AS: ⚠ 即使 code 在此被攔截...
    App->>AS: 4. 換 Token:送出 code + code_verifier(原文)
    Note over AS: 5. 驗證<br/>S256(code_verifier) == 暫存的 code_challenge?
    AS-->>App: 6a. 相符 → 發 Access Token(含 scope)
    Note over AS: 6b. 不符 / 缺 verifier → 拒絕
    App->>FHIR: 7. GET /Patient/123<br/>Authorization: Bearer <token> (走 TLS)
    FHIR-->>App: 8. 驗 Token+Scope 通過 → 回傳 FHIR 資源

關鍵在第 4–5 步:換 Token 時必須附上原始的 code_verifier,授權伺服器把它
即時做 SHA-256,跟先前暫存的 code_challenge 比對。

  • 攻擊者就算在第 3 步攔到授權碼,手上沒有 code_verifier(那東西從沒上過網), 第 4 步就過不了,偷來的 code 變成廢紙。
  • 這就是 PKCE 的本質:用「能不能拿出原始 verifier」來證明「換 Token 的人就是當初發起授權的人」, 取代了 public client 無法保存的 client_secret。

把 Token 用在 FHIR 呼叫上

拿到 Access Token 後,每一次 FHIR API 呼叫都在 HTTP header 帶上它:

GET /fhir/Observation?patient=123&category=laboratory HTTP/1.1
Host: fhir.hospital.example
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

FHIR 伺服器(或前面的 API Gateway)會驗證:

  1. Token 簽章有效、未過期。
  2. Token 的 scope 涵蓋這個資源,例如 patient/Observation.read
  3. SMART launch context 限定的病患範圍是否相符。

Scope 設計與分層防護見 FHIR Box PHI 安全實務指南
SMART on FHIR


一頁速記

flowchart LR
    A[病歷資料<br/>在網路上] -->|加密| TLS[TLS / HTTPS]
    B[授權碼<br/>換 Token 票根] -->|防攔截盜用| PKCE
    C[誰能讀哪些資源] -->|限制| SCOPE[OAuth2 Access Token + Scope]
    style TLS fill:#dfd,stroke:#080
    style PKCE fill:#ffe,stroke:#a80
    style SCOPE fill:#eef,stroke:#669
  • 資料在網路上加密 → TLS,不是 PKCE。
  • PKCE → 防授權碼被攔截冒用,用 code_verifier(不上網)/ code_challenge(SHA-256)配對證明身分,取代 public client 無法保存的 client_secret。
  • OAuth2 Token + Scope → 控管存取範圍
  • 三者疊在一起,才是 SMART on FHIR「安全交換臨床資料」的完整運作機制。

延伸閱讀

© 2025-2026 Nickle Cheng Built with Ruby Ruby on Rails