前言
不同網站的密碼需要管理,不僅容易忘記,也存在被竊取或重複使用帶來的風險。如果只需要掃描指紋或使用臉部識別,就能立即完成註冊與登入——這正是 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)。
- 更新資料庫中的
- 完成登入:實現無密碼安全登入。
- 前端將簽章結果送回後端:
可發現憑證(駐留金鑰)
傳統「不可發現憑證 Non-Discoverable」的驗證方式伺服器必須先知道你是誰(先輸入帳號),伺服器才能從資料庫找出對應的 Credential ID 送給認證器。認證器解密後才能用私鑰簽章。
而現代認證器空間夠大。註冊時,它不僅產生公私鑰,還會把私鑰、使用者 ID、帳號名稱、以及網站的網域 (RP ID) 全部存在認證器。
登入時伺服器不需要知道你是誰,只要問認證器:「你有這個網站(RP ID)的憑證嗎?」認證器就會在本地「發現」這些憑證,並跳出選單問你要用哪一個帳號登入,完全不需要輸入帳號。
註冊
navigator.credentials.create({ publicKey: { // ... 其他設定 authenticatorSelection: { authenticatorAttachment: "platform", // 要求建立可發現憑證 residentKey: "required", userVerification: "required" } }});residentKeyrequired:必須建立可供搜尋的憑證。如果無法建立,系統會傳回NotSupportedError。preferred:RP 偏好建立可供探索的憑證,但也接受不可探索的憑證。discouraged:RP 偏好建立不可偵測的憑證,但也接受可偵測的憑證。
- requireResidentKey:
為了與舊版規格的 WebAuthn Level 1 保持回溯相容性,這個屬性會保留。如果 residentKey 是
required,請將此值設為 true,否則請設為 false。
登入
在發起驗證請求時,你不需要在 allowCredentials 中帶入特定的 Credential ID。
navigator.credentials.get({ publicKey: { challenge: serverGeneratedChallenge, rpId: "yourdomain.com", // 留空或不提供 allowCredentials 陣列 // 認證器會自動根據 rpId 去本地「發現」所有可用的帳號 allowCredentials: [], userVerification: "required" }});總結
FIDO 規格文件有明確的驗證步驟確保不產生安全漏洞,以及不同瀏覽器與平台的差異,實戰上最好依靠現成套件來處理,這也是為什麼會有這麼多 FIDO 套件,把所有複雜情境包裝成 verifyRegistrationResponse() 和 verifyAuthenticationResponse() 能夠直接使用。