前言
不同網站的密碼需要管理,不僅容易忘記,也存在被竊取或重複使用帶來的風險。想像一下,如果只需要掃描指紋或使用臉部識別,就能立即完成註冊與登入——這正是 FIDO(Fast IDentity Online)所推動的「無密碼身分驗證」願景。
透過 FIDO 能享受以下優勢:
- 伺服器沒有儲存密碼就不怕被偷
- 攻擊者無法透過網路攔截,因為私鑰從頭到尾都不會被傳輸
- 綁定網站的來源(Origin)並強制使用 HTTPS 建立安全連線。就算用戶誤入以假亂真的釣魚網站,瀏覽器也不會放行。
- 可搭配生物辨識提升「解鎖私鑰」的安全性
FIDO 大概念
利用非對稱式加密來驗證身份,徹底拋棄帳號密碼
- 挑戰(challenge):每次請求都不同且只能用一次的隨機字串,用於讓私鑰進行簽章
- 簽章(signature):拿私鑰與挑戰進行運算的動作或結果
- FIDO 註冊:用戶產生一組「公鑰 / 私鑰」,網站保存公鑰,用戶保存私鑰
- FIDO 登入:驗證用戶持有私鑰、用戶回應是針對挑戰即時產生的(防重放攻擊)
- 伺服器產生挑戰
- 使用者的裝置用私鑰對挑戰進行簽章
- 伺服器使用公鑰來驗證簽章是否正確
- 生物識別:用戶解鎖私鑰的一種方式,例如指紋。
- Passkey:一種管理私鑰的機制。建構在「私鑰」技術之上的一套數位鑰匙圈系統,讓私鑰變得好懂好管且不怕弄丟。
FIDO 實踐重點
FIDO 之所以可行是因為瀏覽器提供了 Web Authentication API 能安全與私鑰互動、用戶驗證、Origin 綁定、裝置綁定。
用戶 → navigator.credentials.create() → OS / 硬體幫你產 key → 私鑰鎖在裝置 → 公鑰給伺服器
用戶 → navigator.credentials.get() → 用私鑰簽挑戰 → 回傳簽章 → 伺服器用公鑰驗證FIDO 流程圖
註冊
登入
實踐
註冊
-
階段一:生成 (Begin)
- 使用者登入或剛註冊後,申請綁定 Passkey。
- 前端呼叫:
POST /auth/passkey/register/begin。 - 後端回傳資訊:
challenge:一次性隨機字串。user資訊:包含id,name,displayName。relying party(RP):網站 Domain 或rpId。pubKeyCredParams:支援的加密演算法。
- 前端執行
navigator.credentials.create():- 產生公鑰與私鑰、對挑戰進行 Attestation(證明金鑰來源與合法性)
-
階段二:驗證 (Finish)
- 前端將結果送回後端:
POST /auth/passkey/register/finish。 - 後端進行多重驗證:
- 一致性:挑戰是否與先前發出的一致(防重放攻擊)。
- 合法性:Attestation 是否合法(確認裝置與金鑰可信)。
- 來源:Origin /
rpId是否正確(防止釣魚網站)。
- 驗證成功後存檔:
public key、credential id、counter(計數器) 與user關聯。
- 完成註冊:該使用者已成功綁定一組 Passkey。
- 前端將結果送回後端:
登入
-
階段一:生成 (Begin)
- 使用 FIDO 登入
- 前端呼叫:
POST /auth/passkey/login/begin。 - 後端回傳資訊:
challenge:挑戰字串。allowCredentials:該使用者已註冊過的 Credential IDs(若留空則代表使用 Discoverable Credentials 模式)。
- 前端執行
navigator.credentials.get():- 使用者認證:透過指紋、FaceID 或 PIN 碼解鎖私鑰。
- 簽署:使用私鑰對挑戰進行簽章。
-
階段二:驗證 (Finish)
- 前端將簽章結果送回後端:
POST /auth/passkey/login/finish。 - 後端驗證邏輯:
- 挑戰碼:挑戰是否一致。
- 簽章:使用資料庫中的公鑰驗證簽章是否正確。
- 計數器:檢查
counter是否大於前次(防止重放或複製裝置)。 - 來源:Origin /
rpId是否正確。
- 驗證成功後處理:
- 更新資料庫中的
counter。 - 建立登入狀態(簽發 Session 或 JWT)。
- 更新資料庫中的
- 完成登入:實現無密碼安全登入。
- 前端將簽章結果送回後端:
總結
FIDO 規格文件有明確的驗證步驟確保不產生安全漏洞,以及不同瀏覽器與平台的差異,實戰上最好依靠現成套件來處理,這也是為什麼會有這麼多 FIDO 套件,把所有複雜情境包裝成 verifyRegistrationResponse() 和 verifyAuthenticationResponse() 能夠直接使用。