密碼金鑰條件式建立 (登入後宣傳)

本指南將詳細說明如何在使用者成功以密碼登入後,立即自動為使用者註冊密碼金鑰,盡可能減少摩擦,並提高密碼金鑰採用率。

適當的觸發時機

自動建立密碼金鑰 (也稱為條件式建立或登入後自動宣傳) 必須在使用者成功完成涉及密碼的完整登入程序後立即觸發。

  • 請勿嘗試為無密碼流程 (例如魔術連結、簡訊動態密碼或身分聯盟) 建立條件式建立。
  • 如果需要多重驗證,您必須等到所有驗證都成功,才能開始建立條件。
  • 向建立端點發出要求前,請確認有效且經過驗證的使用者工作階段處於啟用狀態。

導入步驟

1. 取消先前的自動填入動作

如果登入頁面使用表單自動填入功能 (條件式 UI/Get),則必須取消有效的憑證 get 呼叫,以免瀏覽器發生衝突。

  • 在呼叫 navigator.credentials.create() 之前,請先對附加至待處理 navigator.credentials.get() 自動填入要求的 AbortController 呼叫 abortController.abort()。

2. 功能偵測

使用 PublicKeyCredential.getClientCapabilities() 檢查 conditionalCreate,判斷是否可使用條件式建立功能。

const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate) {
  // Conditional create is available
}

3. 使用條件式建立功能建立密碼金鑰

  • 在 navigator.credentials.create() 選項中傳遞 mediation: 'conditional'。這會指示瀏覽器在背景或情境中,以不干擾的方式處理密碼金鑰建立流程,不會顯示突兀的強制回應對話方塊。
  • 在 excludeCredentials 中填入使用者現有的密碼金鑰憑證 ID,避免註冊重複的金鑰。

4. 背景錯誤處理

  • 將密碼金鑰建立提示 (navigator.credentials.create) 包裝在 try/catch 區塊中。您必須擷取並忽略一般面向使用者的例外狀況 (InvalidStateError、NotAllowedError、AbortError),不向使用者顯示任何錯誤 UI。

5. 伺服器端使用者在場驗證

  • 伺服器端驗證端點「必須」放寬使用者存在 (UP) 規定 (requireUserPresence: false),但「僅限」驗證條件式建立觸發條件產生的憑證時。標準色情露骨創作必須一律啟用嚴格在場驗證。

6. 妥善處理伺服器驗證失敗

  • 如果 navigator.credentials.create() 成功,但伺服器驗證擷取作業傳回錯誤回應 (例如簽章驗證失敗),請叫用 PublicKeyCredential.signalUnknownCredential(),避免孤立憑證留在密碼金鑰提供者中。

程式碼範例

// optionsFetch and registerVerifyFetch are app-defined server endpoint requests
import { optionsFetch, registerVerifyFetch } from './api.js';

async function triggerConditionalCreate(loginAbortController) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalCreate !== true) {
    return; // Platform does not support conditional creation
  }

  // 1. Abort any active autofill conditional-get controllers to clear the WebAuthn pipeline
  loginAbortController.abort();

  // 2. Fetch creation options signaling the backend that this is a conditional request
  const creationOptionsJSON = await optionsFetch({ conditional: true });
  const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);

  let credential;
  try {
    // 3. Invoke silent credentials creation prompt
    credential = await navigator.credentials.create({ 
      publicKey,
      mediation: 'conditional' // Silent background creation mediation
    });
  } catch (e) {
    // 4. Silently swallow common WebAuthn browser exceptions
    if (['InvalidStateError', 'NotAllowedError', 'AbortError'].includes(e.name)) {
      return; 
    }
    console.error('Unexpected conditional create error:', e);
    return;
  }

  // 5. Server verification step using dedicated Try/Catch block
  let encodedResponse = credential.toJSON();
  try {
    const response = await registerVerifyFetch(encodedResponse);
    if (!response.ok) {
      // If the server verification fails, clean up using Signal API
      if (PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedResponse.id
        });
      }
    }
  } catch (serverErr) {
    console.error('Verification network failure:', serverErr);
    if (PublicKeyCredential.signalUnknownCredential) {
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encodedResponse.id
      });
    }
  }
}

備用策略

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

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

import 'webauthn-polyfills';

Signal API 同步處理備用方案

WebAuthn Signal API (webauthn-signals) 是一種漸進式最佳化功能,可讓密碼管理工具與伺服器憑證狀態保持同步。

  • 備用體驗:透過 if (PublicKeyCredential.signalUnknownCredential) 限制。如果瀏覽器不支援,系統會略過背景驗證同步作業,不會擲回瀏覽器例外狀況。

簡易 JSON 序列化備用方案

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

import 'webauthn-polyfills';