密碼金鑰重新驗證

網路功能: 網路驗證

本指南著重於差異,詳細說明如何為已登入的使用者實作升級驗證或重新驗證,再讓他們執行敏感的帳戶變更 (例如更新密碼、財務轉帳)。

Delta Flow 架構

與一般驗證不同,密碼金鑰重新驗證會嚴格限制密碼金鑰對話方塊提示,只顯示已登入使用者預先註冊的憑證,防止在有效工作階段期間發生帳戶混淆或密碼金鑰假冒。

伺服器端

選項生成差異

建立端點,專門為已知的活躍使用者填入允許的憑證參數:

限制憑證:在 allowCredentials 選項陣列中填入特定 PublicKeyCredentialDescriptor 記錄,對應登入使用者的所有已註冊憑證 ID。如果留空或省略,系統會改用可供偵測的憑證,違反工作階段安全。

// Node.js step-up options generation example
router.post("/api/reauth/options", enforceActiveSession, async (req, res) => {
  const userPasskeys = await db.findCredentialsByUserId(req.user.id);

  const options = {
    challenge: serverGeneratedBase64UrlChallenge, // Random challenge stored in user session
    rpId: "example.com",
    // Enforce allowance strictly limited to the user's credentials list
    allowCredentials: userPasskeys.map((cred) => ({
      type: "public-key",
      id: cred.id,
      transports: cred.transports, // Speeds up resolution by indicating platform transports
    })),
  };
  return res.json(options);
});

驗證端點差異

驗證用戶端傳回的斷言:

驗證帳戶擁有權:驗證端點必須明確驗證,用戶端傳回的已驗證憑證 ID 會解析為儲存的憑證記錄,且相關聯的使用者 ID 與目前登入的使用者 (storedCredential.passkeyUserId === req.user.id) 完全相符。如果傳回的是其他使用者的有效密碼金鑰,則必須立即拒絕驗證。

用戶端流程差異

應用程式會根據交易 UI,從兩種重新驗證介面中選擇:

A. 按鈕流程 (無輸入欄位)

使用者按下「驗證身分」或「繼續交易」按鈕時,觸發重新驗證。

<button id="reauth-btn" data-testid="reauth-button">Confirm Transaction</button>
let reauthAbortController = new AbortController();

async function triggerButtonReauth() {
  // Abort any background suggestion flows to avoid passkey prompt collisions
  reauthAbortController.abort();
  reauthAbortController = new AbortController();

  const optionsResponse = await fetch("/api/reauth/options", {
    method: "POST",
  });
  const optionsJSON = await optionsResponse.json();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);

  try {
    const credential = await navigator.credentials.get({
      publicKey,
      signal: reauthAbortController.signal,
    });

    if (credential) {
      const encodedCredential = credential.toJSON();
      const verifyResponse = await fetch("/api/reauth/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(encodedCredential),
      });

      if (verifyResponse.ok) {
        showTransactionSuccessUI();
      } else if (verifyResponse.status === 404 && PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedCredential.id
        });
      }
    }
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled reauthentication.");
    }
  }
}

document
  .getElementById("reauth-btn")
  .addEventListener("click", triggerButtonReauth);

備用策略

密碼金鑰功能偵測備用方案

請務必安裝「webauthn-polyfills」,並將其匯入結構定義。只要支援 PublicKeyCredential,就支援 PublicKeyCredential.getClientCapabilities。

import 'webauthn-polyfills';

簡易 JSON 序列化備用方案

請務必安裝「webauthn-polyfills」,並將其匯入結構定義。請注意,只要支援 PublicKeyCredential,系統也會支援 PublicKeyCredential.parseRequestOptionsFromJSON 和 PublicKeyCredential.prototype.toJSON。

import 'webauthn-polyfills';