FIDO to achieve passwordless login

實戰 FIDO 驗證實現無密碼登入

前言

不同網站的密碼需要管理,不僅容易忘記,也存在被竊取或重複使用帶來的風險。想像一下,如果只需要掃描指紋或使用臉部識別,就能立即完成註冊與登入——這正是 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 流程圖

註冊

伺服器 (RP Server)FIDO 客戶端 (Client)(如瀏覽器、App)驗證器 (Authenticator)(如手機、安全金鑰)使用者 (User)伺服器 (RP Server)FIDO 客戶端 (Client)(如瀏覽器、App)驗證器 (Authenticator)(如手機、安全金鑰)使用者 (User)驗證器針對該伺服器網域 (RP ID)隨機產生一對全新的「公私鑰 (Key Pair)」驗證器將「私鑰」安全地封裝在硬體晶片內並用私鑰對挑戰碼等資料建立「認證簽章 (Attestation)」伺服器驗證簽章是否有效、挑戰碼是否吻合驗證通過後,將該使用者的「公鑰」與帳號綁定存入資料庫alt[註冊成功][註冊失敗]點擊註冊 / 綁定安全裝置 (Passkey)1發送註冊初始化請求2產生並回傳「挑戰碼 (Challenge)」、伺服器網域 (RP ID) 及使用者資訊3呼叫 WebAuthn API,傳遞挑戰碼與註冊參數4彈出視窗,要求授權建立憑證5進行本地驗證 (如指紋、臉部辨識、PIN 碼)6回傳認證物件 (包含「公鑰」與簽章)7將認證物件送往伺服器完成註冊8註冊完成9顯示裝置綁定成功訊息10拒絕註冊 (如驗證錯誤或超時)11顯示註冊失敗訊息12

登入

伺服器 (RP Server)FIDO 客戶端 (Client)(如瀏覽器、App)驗證器 (Authenticator)(如手機指紋、安全金鑰)使用者 (User)伺服器 (RP Server)FIDO 客戶端 (Client)(如瀏覽器、App)驗證器 (Authenticator)(如手機指紋、安全金鑰)使用者 (User)驗證成功後,驗證器找出當初註冊的私鑰並用該私鑰對「挑戰碼」進行數位簽章伺服器找出該帳號綁定的公鑰驗證簽章是否正確,並確認挑戰碼未被竄改alt[驗證成功][驗證失敗]點擊登入 (輸入帳號或無密碼登入)1發送登入請求2產生並回傳「挑戰」3將挑戰碼與伺服器網域 (RP ID) 交給驗證器4彈出視窗,要求進行本地驗證5進行生物辨識 (如指紋/臉部) 或輸入 PIN 碼6回傳簽章結果 (Assertion)7將簽章結果與驗證資料送回伺服器8允許登入,核發 Session/Token9登入成功,進入系統10拒絕登入11顯示登入失敗訊息12

實踐

註冊

  • 階段一:生成 (Begin)

    1. 使用者登入或剛註冊後,申請綁定 Passkey。
    2. 前端呼叫:POST /auth/passkey/register/begin
    3. 後端回傳資訊:
      • challenge:一次性隨機字串。
      • user 資訊:包含 id, name, displayName
      • relying party (RP):網站 Domain 或 rpId
      • pubKeyCredParams:支援的加密演算法。
    4. 前端執行 navigator.credentials.create()
      • 產生公鑰與私鑰、對挑戰進行 Attestation(證明金鑰來源與合法性)
  • 階段二:驗證 (Finish)

    1. 前端將結果送回後端:POST /auth/passkey/register/finish
    2. 後端進行多重驗證:
      • 一致性:挑戰是否與先前發出的一致(防重放攻擊)。
      • 合法性:Attestation 是否合法(確認裝置與金鑰可信)。
      • 來源:Origin / rpId 是否正確(防止釣魚網站)。
    3. 驗證成功後存檔:
      • public keycredential idcounter (計數器) 與 user 關聯。
    4. 完成註冊:該使用者已成功綁定一組 Passkey。

登入

  • 階段一:生成 (Begin)

    1. 使用 FIDO 登入
    2. 前端呼叫:POST /auth/passkey/login/begin
    3. 後端回傳資訊:
      • challenge:挑戰字串。
      • allowCredentials:該使用者已註冊過的 Credential IDs(若留空則代表使用 Discoverable Credentials 模式)。
    4. 前端執行 navigator.credentials.get()
      • 使用者認證:透過指紋、FaceID 或 PIN 碼解鎖私鑰。
      • 簽署:使用私鑰對挑戰進行簽章。
  • 階段二:驗證 (Finish)

    1. 前端將簽章結果送回後端:POST /auth/passkey/login/finish
    2. 後端驗證邏輯:
      • 挑戰碼:挑戰是否一致。
      • 簽章:使用資料庫中的公鑰驗證簽章是否正確。
      • 計數器:檢查 counter 是否大於前次(防止重放或複製裝置)。
      • 來源:Origin / rpId 是否正確。
    3. 驗證成功後處理:
      • 更新資料庫中的 counter
      • 建立登入狀態(簽發 Session 或 JWT)。
    4. 完成登入:實現無密碼安全登入。

總結

FIDO 規格文件🔗有明確的驗證步驟確保不產生安全漏洞,以及不同瀏覽器與平台的差異,實戰上最好依靠現成套件來處理,這也是為什麼會有這麼多 FIDO 套件,把所有複雜情境包裝成 verifyRegistrationResponse()verifyAuthenticationResponse() 能夠直接使用。