可搜尋的憑證深入探索

雖然密碼金鑰等 FIDO 憑證旨在取代密碼,但大多數的 FIDO 憑證也可以讓使用者不必輸入使用者名稱。這樣使用者就能從目前網站的密碼金鑰清單中選取帳戶,進行驗證。

舊版的安全金鑰是專為兩步驟驗證方法而設計,需要潛在憑證的 ID,因此需要輸入使用者名稱。安全金鑰可在不知道 ID 的情況下找到的憑證稱為可探索的憑證。目前建立的大多數 FIDO 憑證都是可偵測的憑證,尤其是儲存在密碼管理工具或新式安全金鑰中的密碼金鑰。

如要確保您的憑證會建立為密碼金鑰 (可供搜尋的憑證),請在建立憑證時指定 residentKeyrequireResidentKey

依賴方 (RP) 可以在密碼金鑰驗證期間省略 allowCredentials,以便使用可偵測的憑證。在這種情況下,瀏覽器或系統會向使用者顯示可用密碼金鑰清單,並在建立時透過 user.name 屬性集合識別。如果使用者選取其中一個,user.id 值就會納入產生的簽名中。接著,伺服器可以使用該 ID 或傳回的憑證 ID 來查詢帳戶,而非輸入的使用者名稱。

帳戶選取器 UI (如前述所述) 絕不會顯示無法偵測到的憑證。

requireResidentKeyresidentKey

如要建立密碼金鑰,請在 navigator.credentials.create() 上指定 authenticatorSelection.residentKeyauthenticatorSelection.requireResidentKey,並使用以下值。

async function register () {
  // ...

  const publicKeyCredentialCreationOptions = {
    // ...
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      residentKey: 'required',
      requireResidentKey: true,
    }
  };

  const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // ...
}

residentKey

  • 'required':必須建立可供搜尋的憑證。如果無法建立,系統會傳回 NotSupportedError
  • 'preferred':RP 偏好建立可供搜尋的憑證,但接受不開放搜尋的憑證。
  • 'discouraged':RP 偏好建立不可偵測的憑證,但也接受可偵測的憑證。

requireResidentKey

  • 為了與舊版規格 WebAuthn Level 1 的回溯相容性,此屬性會保留。如果 residentKey'required',請將這個屬性設為 true,否則請設為 false

allowCredentials

RP 可以使用 navigator.credentials.get() 上的 allowCredentials 控管密碼金鑰驗證體驗。密碼金鑰驗證體驗通常有三種類型:

有了可探索的憑證,RP 就能顯示模式帳戶選擇器,讓使用者選取要登入的帳戶,然後進行使用者驗證。此設定適用於使用者按下密碼金鑰驗證專用按鈕後,啟動的密碼金鑰驗證流程。

如要提供這種使用者體驗,請略過或傳遞空陣列至 navigator.credentials.get() 中的 allowCredentials 參數。

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

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

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

顯示密碼金鑰表單自動填入功能

如果大多數使用者都使用密碼金鑰,且這些密碼金鑰可在本機裝置上使用,上述的模態帳戶選取器就會發揮作用。如果使用者沒有本機密碼金鑰,系統仍會顯示強制回應對話方塊,並為使用者提供使用其他裝置的密碼金鑰。在將使用者轉換為密碼金鑰時,您可能會想避免為尚未設定密碼金鑰的使用者顯示該 UI。

相反地,系統可能會在傳統登入表單中將選取的密碼金鑰,連同已儲存的使用者名稱和密碼,一併填寫到自動填入提示中填寫的欄位。這樣一來,如果使用者有密碼金鑰,就能選取密碼金鑰來「填入」登入表單;如果使用者有儲存的使用者名稱/密碼組合,就能選取這些項目;如果使用者沒有這兩者,仍可輸入使用者名稱和密碼。

在使用者同時採用密碼和密碼金鑰遷移 RP 時,提供理想的使用者體驗。

如要提供這項使用者體驗,除了將空白陣列傳遞至 allowCredentials 屬性或省略參數之外,請在 navigator.credentials.get() 中指定 mediation: 'conditional',並使用 autocomplete="username webauthn"password 輸入欄位為 HTML username 輸入欄位加上註解。autocomplete="password webauthn"

呼叫 navigator.credentials.get() 不會顯示任何 UI,但如果使用者將焦點放在已註解的輸入欄位,則所有可用的密碼金鑰都會納入自動填入選項。如果使用者選取任一選項,系統就會進行一般裝置解鎖驗證,且只會完成 .get() 傳回的承諾並取得結果。如果使用者未選取密碼金鑰,則承諾永遠不會解析。

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const cred = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal,
    // Specify 'conditional' to activate conditional UI
    mediation: 'conditional'
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
<input type="text" name="username" autocomplete="username webauthn" ...>

如要瞭解如何建構這類使用者體驗,請參閱「透過表單自動填入功能使用密碼金鑰登入」和「在網路應用程式中使用表單自動填入功能實作密碼金鑰」程式碼研究室。

重新驗證

在某些情況下 (例如使用密碼金鑰重新驗證時),系統已知道使用者的 ID。在此情況下,要使用密碼金鑰,但不在瀏覽器或作業系統中顯示任何形式的帳戶選取器。您可以透過在 allowCredentials 參數中傳遞憑證 ID 清單來達成這項目標。

在這種情況下,如果有任何指定憑證可在本機使用,系統就會立即提示使用者解鎖裝置。如果沒有,系統會提示使用者出示另一部裝置 (手機或安全金鑰),以便提供有效的憑證。

為提供這項使用者體驗,請提供登入使用者的憑證 ID 清單。由於使用者已知,RP 應可查詢這些資訊。在 navigator.credentials.get()allowCredentials 屬性中,以 PublicKeyCredentialDescriptor 物件形式提供憑證 ID。

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // Provide a list of PublicKeyCredentialDescriptors:
    allowCredentials: [{
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, {
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, ...]
  };

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

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

PublicKeyCredentialDescriptor 物件包含:

  • id:RP 在密碼金鑰註冊作業中取得的公開金鑰憑證 ID。
  • type:這個欄位通常是 'public-key'
  • transports:持有此憑證的裝置支援的傳輸機制提示,可供瀏覽器用來最佳化要求使用者出示外部裝置的 UI。這份清單 (如有提供) 應包含每個憑證註冊期間呼叫 getTransports() 的結果。

摘要

有了可偵測的憑證,使用者就不必輸入使用者名稱,使用密碼金鑰登入體驗會更加容易。RP 結合 residentKeyrequireResidentKeyallowCredentials 後,即可實現以下登入體驗:

  • 顯示強制回應帳戶選取器。
  • 顯示密碼金鑰表單自動填入。
  • 重新驗證。

謹慎使用可搜尋的憑證。這樣一來,您就能設計精密的密碼金鑰登入體驗,讓使用者獲得流暢的登入體驗,和他們互動的可能性更高。