為無密碼登入建立密碼金鑰

密碼金鑰可讓使用者帳戶更安全、更簡單、更容易使用。

使用密碼金鑰可強化安全性、簡化登入程序,並取代密碼。與使用者必須手動記住及輸入的一般密碼不同,密碼金鑰會使用裝置的螢幕鎖定機制 (例如生物特徵辨識或 PIN 碼),降低網路釣魚風險和憑證竊取行為。

密碼金鑰會透過密碼金鑰提供者 (例如 Google 密碼管理工具和 iCloud 鑰匙圈) 在裝置間同步。

您必須建立密碼金鑰,將私密金鑰安全地儲存在密碼金鑰供應器中,並將必要的中繼資料和公開金鑰儲存在伺服器上,以便驗證。私密金鑰會在使用者驗證有效網域後發出簽名,讓密碼金鑰不易遭受網路釣魚攻擊。公開金鑰可驗證簽名,但不會儲存敏感憑證,因此密碼金鑰不易遭到憑證竊取。

建立密碼金鑰的運作方式

在使用者能透過密碼金鑰登入之前,您必須建立密碼金鑰、將金鑰與使用者帳戶建立關聯,並將金鑰的公開金鑰儲存在伺服器上。

您可以在下列任一情況下要求使用者建立密碼金鑰:

  • 註冊期間或註冊後。
  • 登入後。
  • 使用其他裝置的密碼金鑰登入後 (也就是 [authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment)cross-platform)。
  • 在使用者可管理密碼金鑰的專屬頁面。

如要建立密碼金鑰,請使用 WebAuthn API

密碼金鑰註冊流程的四個元件如下:

  • 後端:儲存使用者帳戶詳細資料,包括公開金鑰。
  • 前端:與瀏覽器通訊,並從後端擷取必要資料。
  • 瀏覽器:執行 JavaScript 並與 WebAuthn API 互動。
  • 密碼金鑰供應商:建立並儲存密碼金鑰。這通常是密碼管理工具 (例如 Google 密碼管理工具) 或安全金鑰。
建立及註冊密碼金鑰的程序
建立及註冊密碼金鑰的程序。

建立密碼金鑰之前,請確認系統符合下列先決條件:

  • 使用者帳戶會在短時間內透過安全方法 (例如電子郵件、電話驗證或身分聯合) 進行驗證。

  • 前端和後端可安全地進行通訊,交換憑證資料。

  • 瀏覽器支援 WebAuthn 和密碼金鑰建立功能。

我們會在後續章節說明如何檢查大部分的項目。

系統符合上述條件後,就會透過以下程序建立密碼金鑰:

  1. 當使用者啟動動作 (例如在密碼金鑰管理頁面中點選「建立密碼金鑰」按鈕,或在完成註冊後) 時,系統就會觸發密碼金鑰建立程序。
  2. 前端會向後端要求必要的憑證資料,包括使用者資訊、挑戰和憑證 ID,以免重複。
  3. 前端會呼叫 navigator.credentials.create(),提示裝置的密碼金鑰供應器使用後端的資訊產生密碼金鑰。請注意,這個呼叫會傳回承諾。
  4. 使用者的裝置會使用生物特徵辨識方法、PIN 碼或圖案驗證使用者,藉此建立密碼金鑰。
  5. 密碼金鑰提供者會建立密碼金鑰,並將公開金鑰憑證傳回至前端,藉此解析承諾。
  6. 前端會將產生的公開金鑰憑證傳送至後端。
  7. 後端會儲存公開金鑰和其他重要資料,以利日後驗證。
  8. 後端會通知使用者 (例如透過電子郵件),確認密碼金鑰建立作業,並偵測潛在的未經授權存取行為。

這項程序可確保使用者能安全順利地註冊密碼金鑰。

相容性

大多數瀏覽器都支援 WebAuthn,但仍有些許差異。如需瀏覽器和作業系統相容性詳細資訊,請參閱 passkeys.dev

建立新的密碼金鑰

如要建立新的密碼金鑰,前端應遵循下列程序:

  1. 檢查相容性
  2. 從後端擷取資訊。
  3. 呼叫 WebAuth API 以建立密碼金鑰。
  4. 將傳回的公開金鑰傳送至後端。
  5. 儲存憑證。

請參閱以下各節瞭解如何操作。

檢查相容性

在顯示「建立新密碼金鑰」按鈕之前,前端應檢查以下事項:

  • 瀏覽器支援使用 PublicKeyCredential 的 WebAuthn。

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

  • 裝置支援使用 PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() 的平台驗證器 (可建立密碼金鑰,並使用密碼金鑰進行驗證)。

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

  • 瀏覽器支援使用 PublicKeyCredenital.isConditionalMediationAvailable()WebAuthn 條件式 UI

Browser Support

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Source

下列程式碼片段說明如何在顯示密碼金鑰相關選項前,先檢查相容性。

// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  

在本例中,只有在符合所有條件時,系統才會顯示「Create a new passkey」按鈕。

從後端擷取資訊

使用者按下按鈕時,從後端擷取必要資訊,以便呼叫 navigator.credentials.create()

下列程式碼片段顯示 JSON 物件,其中包含呼叫 navigator.credentials.create() 所需的資訊:

// Example `PublicKeyCredentialCreationOptions` contents
{
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{
    alg: -7, type: "public-key"
  },{
    alg: -257, type: "public-key"
  }],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

物件中的鍵/值組合會保留下列資訊:

  • challenge:伺服器為此註冊產生的 ArrayBuffer 挑戰。
  • rp.id:RP ID (依賴方 ID)、網域和網站可以指定其網域或可註冊的後置字串。舉例來說,如果 RP 的來源是 https://login.example.com:1337,RP ID 可以是 login.example.comexample.com。如果 RP ID 指定為 example.com,使用者可以在 login.example.comexample.com 的任何子網域上進行驗證。如需更多資訊,請參閱「透過相關原始網址要求,允許在各個網站重複使用密碼金鑰」。
  • rp.name:RP (依賴方) 名稱。這項功能已在 WebAuthn L3 淘汰,但為了相容性而保留。
  • user.id:ArrayBuffer 中的專屬使用者 ID,在建立帳戶時產生。這項資訊應為永久不變,不像使用者名稱可供編輯。使用者 ID 可識別帳戶,但不得包含任何個人識別資訊 (PII)。系統中可能已包含使用者 ID,但如有需要,請專門為密碼金鑰建立一個,以免出現任何 PII。
  • user.name:使用者能夠認得的帳戶專屬 ID,例如電子郵件地址或使用者名稱。這會顯示在帳戶選取器中。
  • user.displayName:帳戶的名稱 (必須填寫),使用者會更容易辨識。這個名稱不必是唯一的,也可以是使用者選擇的名稱。如果您的網站沒有適合用於此處的值,請傳遞空字串。視瀏覽器而定,這可能會顯示在帳戶選取器中。
  • pubKeyCredParams:指定 RP (依賴方) 支援的公開金鑰演算法。建議將其設為 [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]。這項設定會指定支援 ECDSA 與 P-256 和 RSA PKCS#1,而支援這些項目可提供完整涵蓋。
  • excludeCredentials:已註冊憑證 ID 的清單。提供已註冊憑證 ID 的清單,避免註冊相同裝置兩次transports 成員 (如有提供) 應包含註冊各個憑證時呼叫 getTransports() 的結果。
  • authenticatorSelection.authenticatorAttachment:如果密碼金鑰建立作業是從密碼升級 (例如在登入後的促銷活動中),請將此值設為 "platform"hint: ['client-device']"platform" 表示 RP 想要平台驗證器 (嵌入平台裝置的驗證器),不會提示使用者插入 USB 安全金鑰等。使用者可以透過更簡單的方式建立密碼金鑰。
  • authenticatorSelection.requireResidentKey:將其設為布林值 true可偵測的憑證 (本機金鑰)會將使用者資訊儲存至密碼金鑰,並讓使用者在驗證後選取帳戶。
  • authenticatorSelection.userVerification:指出使用裝置螢幕鎖定功能進行使用者驗證的結果是 "required""preferred" 還是 "discouraged"。預設值為 "preferred",這表示驗證工具可能會略過使用者驗證程序。將其設為 "preferred" 或省略該屬性。

建議您在伺服器上建構物件,並使用 Base64URL 編碼 ArrayBuffer,然後從前端擷取。這樣一來,您就可以使用 PublicKeyCredential.parseCreationOptionsFromJSON() 解碼酬載,並直接將其傳遞至 navigator.credentials.create()

下列程式碼片段說明如何擷取及解碼建立密碼金鑰所需的資訊。

// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');

// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...

呼叫 WebAuthn API 以建立密碼金鑰

呼叫 navigator.credentials.create() 即可建立新密碼金鑰。API 會傳回承諾,等待使用者互動以顯示模式對話方塊。

Browser Support

  • Chrome: 60.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
  publicKey: options
});

將傳回的公開金鑰憑證傳送至後端

使用者透過裝置的螢幕鎖定功能通過驗證後,系統會建立密碼金鑰,並解析承諾,將 PublicKeyCredential 物件傳回至前端。

承諾可能會因為各種原因遭到拒絕。您可以檢查 Error 物件的 name 屬性,藉此處理這些錯誤:

  • InvalidStateError:裝置上已存在密碼金鑰。系統不會向使用者顯示錯誤對話方塊。網站不應將此視為錯誤。使用者希望註冊本機裝置,且已註冊。
  • NotAllowedError:使用者已取消作業。
  • AbortError:作業已終止。
  • 其他例外狀況:發生非預期的錯誤,瀏覽器會向使用者顯示錯誤對話方塊。

公開金鑰憑證物件包含下列屬性:

  • id:所建立密碼金鑰的 Base64URL 編碼 ID。這個 ID 可在驗證時,協助瀏覽器判斷裝置上是否有相符的密碼金鑰。這個值必須儲存在後端資料庫中。
  • rawId:憑證 ID 的 ArrayBuffer 版本。
  • response.clientDataJSON:ArrayBuffer 編碼的用戶端資料。
  • response.attestationObject:ArrayBuffer 編碼的認證物件。當中包含 RP ID、旗標和公開金鑰等重要資訊。
  • authenticatorAttachment:如果是在支援密碼金鑰的裝置上建立這組憑證,就會傳回 "platform"
  • type:這個欄位一律設為 "public-key"

使用 .toJSON() 方法對物件進行編碼,然後使用 JSON.stringify() 序列化物件,再將其傳送至伺服器。

...

// Encode and serialize the `PublicKeyCredential`.
const _result = credential.toJSON();
const result = JSON.stringify(_result);

// Encode and send the credential to the server for verification.  
const response = await fetch('/webauthn/registerResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});
...

儲存憑證

在後端收到公開金鑰憑證後,建議您使用伺服器端程式庫或解決方案,而不要自行編寫程式碼來處理公開金鑰憑證。

接著,您可以將從憑證擷取的資訊儲存到資料庫,以供日後使用。

以下清單列出建議儲存的屬性:

  • 憑證 ID:透過公開金鑰憑證傳回的憑證 ID。
  • 憑證名稱:憑證的名稱。請以建立密碼金鑰的供應商名稱命名,這樣就能根據 AAGUID 進行識別。
  • 使用者 ID:用於建立密碼金鑰的使用者 ID。
  • 公開金鑰:透過公開金鑰憑證傳回的公開金鑰。這是驗證密碼金鑰斷言的必要條件。
  • 建立日期和時間:記錄密碼金鑰建立的日期和時間。這有助於識別密碼金鑰。
  • 上次使用日期和時間:記錄使用者上次使用密碼金鑰登入的日期和時間。這有助於判斷使用者已使用 (或未使用) 哪些密碼金鑰。
  • AAGUID:密碼金鑰供應商的專屬 ID。
  • 備份資格標記:如果裝置符合密碼鍵同步處理資格,則為 true。這項資訊可協助使用者在密碼金鑰管理頁面上,找出可同步的密碼金鑰和裝置綁定 (無法同步) 的密碼金鑰。

請按照伺服器端密碼金鑰註冊中的詳細操作說明

註冊失敗時發出信號

如果密碼金鑰註冊失敗,可能會讓使用者感到困惑。如果密碼金鑰提供者中有密碼金鑰,且可供使用者使用,但相關聯的公開金鑰未儲存在伺服器端,則使用密碼金鑰登入的嘗試永遠不會成功,且難以排除問題。請務必讓使用者知道這項情況。

為避免這種情況,您可以使用 Signal API 向密碼金鑰供應商發出未知密碼金鑰的信號。透過使用 RP ID 和憑證 ID 呼叫 PublicKeyCredential.signalUnknownCredential(),RP 可向密碼金鑰提供者告知已移除或不存在指定憑證。密碼金鑰供應商會決定如何處理這項信號,但如果支援,則預期會移除相關聯的密碼金鑰。

// Detect authentication failure due to lack of the credential
if (response.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

如要進一步瞭解 Signal API,請參閱「使用 Signal API 讓密碼金鑰與伺服器上的憑證保持一致」。

向使用者傳送通知

在註冊密碼金鑰時傳送通知 (例如電子郵件),有助於使用者偵測未經授權的帳戶存取行為。如果攻擊者在使用者不知情的情況下建立密碼金鑰,即使密碼已變更,密碼金鑰仍可用於日後的濫用行為。這則通知會提醒使用者,並協助避免發生這種情況。

檢查清單

  • 請先驗證使用者 (最好使用電子郵件或安全方法),再允許他們建立密碼金鑰。
  • 使用 excludeCredentials 時,避免為相同的密碼金鑰供應商建立重複的密碼金鑰。
  • 儲存 AAGUID 以識別密碼金鑰供應器,並為使用者命名憑證。
  • 若嘗試註冊密碼金鑰失敗,請使用 PublicKeyCredential.signalUnknownCredential() 發出信號。
  • 為使用者建立並註冊帳戶密碼金鑰後,傳送通知給使用者。

資源

後續步驟:透過表單自動填入功能使用密碼金鑰登入