密碼金鑰可讓使用者帳戶更安全、更簡單、更容易使用。
使用密碼金鑰可強化安全性、簡化登入程序,並取代密碼。與使用者必須手動記住及輸入的一般密碼不同,密碼金鑰會使用裝置的螢幕鎖定機制 (例如生物特徵辨識或 PIN 碼),降低網路釣魚風險和憑證竊取行為。
密碼金鑰會透過密碼金鑰提供者 (例如 Google 密碼管理工具和 iCloud 鑰匙圈) 在裝置間同步。
您必須建立密碼金鑰,將私密金鑰安全地儲存在密碼金鑰供應器中,並將必要的中繼資料和公開金鑰儲存在伺服器上,以便驗證。私密金鑰會在使用者驗證有效網域後發出簽名,讓密碼金鑰不易遭受網路釣魚攻擊。公開金鑰可驗證簽名,但不會儲存敏感憑證,因此密碼金鑰不易遭到憑證竊取。
建立密碼金鑰的運作方式
在使用者能透過密碼金鑰登入之前,您必須建立密碼金鑰、將金鑰與使用者帳戶建立關聯,並將金鑰的公開金鑰儲存在伺服器上。
您可以在下列任一情況下要求使用者建立密碼金鑰:
- 註冊期間或註冊後。
- 登入後。
- 使用其他裝置的密碼金鑰登入後 (也就是
[authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment)
是cross-platform
)。 - 在使用者可管理密碼金鑰的專屬頁面。
如要建立密碼金鑰,請使用 WebAuthn API。
密碼金鑰註冊流程的四個元件如下:
- 後端:儲存使用者帳戶詳細資料,包括公開金鑰。
- 前端:與瀏覽器通訊,並從後端擷取必要資料。
- 瀏覽器:執行 JavaScript 並與 WebAuthn API 互動。
- 密碼金鑰供應商:建立並儲存密碼金鑰。這通常是密碼管理工具 (例如 Google 密碼管理工具) 或安全金鑰。

建立密碼金鑰之前,請確認系統符合下列先決條件:
使用者帳戶會在短時間內透過安全方法 (例如電子郵件、電話驗證或身分聯合) 進行驗證。
前端和後端可安全地進行通訊,交換憑證資料。
瀏覽器支援 WebAuthn 和密碼金鑰建立功能。
我們會在後續章節說明如何檢查大部分的項目。
系統符合上述條件後,就會透過以下程序建立密碼金鑰:
- 當使用者啟動動作 (例如在密碼金鑰管理頁面中點選「建立密碼金鑰」按鈕,或在完成註冊後) 時,系統就會觸發密碼金鑰建立程序。
- 前端會向後端要求必要的憑證資料,包括使用者資訊、挑戰和憑證 ID,以免重複。
- 前端會呼叫
navigator.credentials.create()
,提示裝置的密碼金鑰供應器使用後端的資訊產生密碼金鑰。請注意,這個呼叫會傳回承諾。 - 使用者的裝置會使用生物特徵辨識方法、PIN 碼或圖案驗證使用者,藉此建立密碼金鑰。
- 密碼金鑰提供者會建立密碼金鑰,並將公開金鑰憑證傳回至前端,藉此解析承諾。
- 前端會將產生的公開金鑰憑證傳送至後端。
- 後端會儲存公開金鑰和其他重要資料,以利日後驗證。
- 後端會通知使用者 (例如透過電子郵件),確認密碼金鑰建立作業,並偵測潛在的未經授權存取行為。
這項程序可確保使用者能安全順利地註冊密碼金鑰。
相容性
大多數瀏覽器都支援 WebAuthn,但仍有些許差異。如需瀏覽器和作業系統相容性詳細資訊,請參閱 passkeys.dev。
建立新的密碼金鑰
如要建立新的密碼金鑰,前端應遵循下列程序:
請參閱以下各節瞭解如何操作。
檢查相容性
在顯示「建立新密碼金鑰」按鈕之前,前端應檢查以下事項:
- 瀏覽器支援使用
PublicKeyCredential
的 WebAuthn。
- 裝置支援使用
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
的平台驗證器 (可建立密碼金鑰,並使用密碼金鑰進行驗證)。
- 瀏覽器支援使用
PublicKeyCredenital.isConditionalMediationAvailable()
的 WebAuthn 條件式 UI。
下列程式碼片段說明如何在顯示密碼金鑰相關選項前,先檢查相容性。
// 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.com
或example.com
。如果 RP ID 指定為example.com
,使用者可以在login.example.com
或example.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 會傳回承諾,等待使用者互動以顯示模式對話方塊。
// 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()
發出信號。 - 為使用者建立並註冊帳戶密碼金鑰後,傳送通知給使用者。
資源
後續步驟:透過表單自動填入功能使用密碼金鑰登入。