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

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

發布日期:2022 年 10 月 12 日,上次更新時間:2026 年 4 月 9 日

使用密碼金鑰可提升安全性、簡化登入程序,並取代密碼。密碼金鑰與一般密碼不同,使用者不必記住密碼並手動輸入,而是透過裝置的螢幕鎖定機制 (例如生物辨識或 PIN 碼) 登入,有助於降低網路釣魚和憑證遭竊的風險。

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

您必須建立密碼金鑰,將私密金鑰安全地儲存在密碼金鑰供應商,並將必要的中繼資料和公開金鑰儲存在伺服器上,以進行驗證。私密金鑰會在有效網域上驗證使用者後發出簽章,因此密碼金鑰可防範網路釣魚。公開金鑰會驗證簽章,且不會儲存機密憑證,因此密碼金鑰可防範憑證遭竊。

建立密碼金鑰的運作方式

如要透過密碼金鑰登入,使用者必須建立密碼金鑰並連結至使用者帳戶,再將其公開金鑰儲存在伺服器上。

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

  • 註冊期間或註冊後。
  • 登入後。
  • 使用其他裝置的密碼金鑰登入後 (即authenticatorAttachmentcross-platform)。
  • 在專屬頁面,使用者可管理密碼金鑰。

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

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

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

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

  • 在有意義的短時間內,透過安全方法 (例如電子郵件、電話驗證或身分同盟) 驗證使用者帳戶。

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

  • 瀏覽器支援 WebAuthn 和密碼金鑰建立作業。

我們會在下文說明如何檢查大部分的項目。

系統滿足這些條件後,就會執行下列程序來建立密碼金鑰:

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

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

適用情形

大多數瀏覽器都支援 WebAuthn,但仍有少許缺漏。 如需瀏覽器和作業系統的相容性詳細資料,請參閱 passkeys.dev

建立新的密碼金鑰

如要建立新密碼金鑰,前端應按照下列程序操作:

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

以下各節說明如何執行這項操作。

確認相容性

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

  • 瀏覽器支援 WebAuthn,且支援 PublicKeyCredential

Browser Support

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

Source

  • 瀏覽器支援使用 PublicKeyCredential.getClientCapabilities() 偵測功能。

Browser Support

  • Chrome: 133.
  • Edge: 133.
  • Firefox: 135.
  • Safari: 17.4.

Source

  • 瀏覽器支援 WebAuthn 條件式 UI,並使用 conditionalGet

  • 裝置支援平台驗證器 (可在裝置上建立密碼金鑰並進行驗證),且支援 passkeyPlatformAuthenticator

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

if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalGet === true &&
      capabilities.passkeyPlatformAuthenticator === true) {
    // The browser supports passkeys and the conditional UI.
  }
}

在本例中,只有在符合所有條件時,才應顯示「建立新密碼金鑰」按鈕。

從後端擷取資訊

使用者點選按鈕時,請從後端擷取必要資訊,然後呼叫 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.idRP 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,但如有需要,請專為密碼金鑰建立 ID,確保其中不含任何 PII。
  • user.name: 使用者能夠認得的帳戶專屬 ID,例如電子郵件地址或使用者名稱。這會顯示在帳戶選取器中。
  • user.displayName:帳戶的必填名稱,方便使用者辨識。這個名稱可以重複,且可以是使用者選擇的名稱。如果網站沒有適合在此處加入的值,請傳遞空字串。視瀏覽器而定,這可能會顯示在帳戶選取器中。
  • pubKeyCredParams: 指定 RP (信賴方) 支援的公開金鑰演算法。建議設為 [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]。這會指定支援使用 P-256 的 ECDSA 和 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 會傳回 Promise,等待使用者互動並顯示模式對話方塊。

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
});

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

使用裝置的螢幕鎖定驗證使用者後,系統會建立密碼金鑰並解析 Promise,將 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 可以使用 RP ID 和憑證 ID 呼叫 PublicKeyCredential.signalUnknownCredential(),通知密碼金鑰提供者指定的憑證已移除或不存在。密碼金鑰供應商可自行決定如何處理這項信號,但如果支援這項功能,系統應會移除相關聯的密碼金鑰。

// 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() 註冊密碼金鑰失敗,請發出信號。
  • 為使用者的帳戶建立及註冊密碼金鑰後,傳送通知給使用者。

資源

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