建立登入服務,讓使用者可透過密碼金鑰登入,同時也能使用現有的密碼。
本指南說明如何使用表單自動填入功能,讓使用者可透過密碼金鑰和密碼登入。使用表單自動填入功能可提供一致的登入體驗,簡化從密碼轉換為更安全且使用者友善的密碼金鑰驗證方法。
瞭解如何導入 WebAuthn 的條件式 UI,在現有登入表單中支援密碼金鑰和密碼使用者,並盡量減少使用者操作的阻礙。
為什麼要使用表單自動填入功能搭配密碼金鑰登入帳戶?
密碼金鑰可讓使用者透過指紋、臉部辨識或裝置 PIN 碼登入網站。
如果所有使用者都有密碼金鑰,驗證流程可以是單一登入按鈕。輕觸按鈕後,使用者就能直接透過螢幕鎖定功能驗證帳戶,然後登入。
不過,從密碼轉換為密碼金鑰並非易事。網站必須在這段期間內同時支援密碼和密碼金鑰使用者。要求使用者記得哪些網站使用密碼金鑰,並要求他們預先選擇登入方式,會導致使用者體驗不佳。
密碼金鑰也是新技術,因此要清楚說明可能不太容易。使用熟悉的自動填入介面,有助於解決轉換挑戰,並滿足使用者熟悉的需要。
使用條件式 UI
如要有效支援密碼金鑰和密碼使用者,請在表單的自動填入建議中加入密碼金鑰。這個方法會使用 條件式 UI,這是 WebAuthn 標準的一項功能。
當使用者將焦點放在使用者名稱輸入欄位時,系統會顯示自動填入對話方塊,建議使用者儲存的密碼金鑰和已儲存的密碼。使用者可以選擇密碼金鑰或密碼,然後繼續登入,如果選擇密碼金鑰,則可使用裝置螢幕鎖定功能。
這樣一來,使用者就能透過現有的登入表單登入網站,但如果他們有密碼金鑰,還能享有額外的安全性優勢。
密碼金鑰驗證機制的運作方式
如要使用密碼金鑰進行驗證,請使用 WebAuthn API。
密碼金鑰驗證流程中的四個元件如下:
- 後端:儲存使用者帳戶詳細資料,包括公開金鑰。
- 前端:與瀏覽器通訊,並從後端擷取必要資料。
- 瀏覽器:執行 JavaScript 並與 WebAuthn API 互動。
- 密碼金鑰供應商:建立並儲存密碼金鑰。通常是密碼管理工具 (例如 Google 密碼管理工具) 或安全金鑰。

密碼金鑰驗證程序的流程如下:
- 使用者造訪登入頁面,而前端會向後端要求驗證挑戰。
- 後端會產生並傳回與使用者帳戶相關聯的 WebAuthn 挑戰。
- 前端會使用挑戰程序呼叫
navigator.credentials.get()
,以便透過瀏覽器啟動驗證程序。 - 瀏覽器會與密碼金鑰提供者互動,提示使用者選取密碼金鑰 (通常會使用自動填入對話方塊,並在登入欄位聚焦時觸發),然後使用裝置螢幕鎖定或生物特徵辨識功能驗證身分。
- 使用者驗證成功後,密碼金鑰提供者會簽署挑戰,而瀏覽器會將產生的公開金鑰憑證 (包括簽名) 傳回至前端。
- 前端會將這組憑證傳送至後端。
- 後端會根據使用者儲存的公開金鑰,驗證憑證的簽名。如果驗證成功,後端就會讓使用者登入。
透過表單自動填入功能使用密碼金鑰進行驗證
如要使用表單自動填入功能啟動密碼金鑰驗證,請在登入頁面載入時發出條件式 WebAuthn get
呼叫。這個對 navigator.credentials.get()
的呼叫包含 mediation: 'conditional'
選項。
對 WebAuthn 的 navigator.credentials.get()
API 提出的條件式要求不會立即顯示 UI。而是處於待處理狀態,直到使用者與使用者名稱欄位的自動填入提示互動為止。如果使用者選取密碼金鑰,瀏覽器會使用憑證解析待處理的承諾,藉此讓使用者登入,而無須透過傳統表單提交方式。如果使用者改為選擇密碼,則系統不會解析承諾,並繼續執行標準密碼登入流程。此時,頁面必須負責讓使用者登入。
為表單輸入欄位加上註解
如要啟用密碼金鑰自動填入功能,請將 autocomplete
屬性新增至表單的使用者名稱 input
欄位。請將 username
和 webauthn
一併納入,並以空格分隔。
<input type="text" name="username" autocomplete="username webauthn" autofocus>
將 autofocus
新增至這個欄位後,系統會在頁面載入時自動觸發自動填入提示,並立即顯示可用的密碼和通行密鑰。
特徵偵測
在叫用條件式 WebAuthn API 呼叫之前,請檢查以下事項:
- 瀏覽器支援使用
PublicKeyCredential
的 WebAuthn。
- 瀏覽器支援使用
PublicKeyCredential.isConditionalMediationAvailable()
的 WebAuthn 條件式 UI。
下列程式碼片段說明如何檢查瀏覽器是否支援這些功能:
// Availability of window.PublicKeyCredential means WebAuthn is usable.
if (window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable) {
// Check if conditional mediation is available.
const isCMA = await PublicKeyCredential.isConditionalMediationAvailable();
if (isCMA) {
// Call WebAuthn authentication
}
}
從後端擷取資訊
後端需要向前端提供幾個選項,才能啟動 navigator.credentials.get()
呼叫。這些選項通常會從伺服器端點以 JSON 物件的形式擷取。
選項物件中的重點屬性包括:
challenge
:伺服器在 ArrayBuffer 中產生的挑戰 (通常會為 JSON 傳輸進行 Base64URL 編碼)。這對於防範重播攻擊至關重要。您的伺服器必須為每次登入嘗試產生新的挑戰,並應在短時間後或嘗試失敗時使其失效。allowCredentials
:憑證描述項陣列。傳入空陣列。這會促使瀏覽器列出指定rpId
的所有憑證。userVerification
:指定使用者驗證偏好設定,例如要求使用裝置螢幕鎖定。預設值為"preferred"
,也是建議值。可能的值如下:"required"
:驗證程序必須由驗證工具 (例如 PIN 碼或生物特徵辨識) 執行。如果無法執行驗證,作業就會失敗。"preferred"
:驗證工具嘗試驗證使用者,但即使不驗證,作業仍可成功。"discouraged"
:驗證工具應盡可能避免使用者驗證。
userVerification
rpId
:依賴方 ID,通常是網站的網域 (例如example.com
)。這個值必須與建立密碼金鑰憑證時使用的rp.id
完全相符。
您的伺服器應建構這個選項物件。ArrayBuffer
值 (例如 challenge
) 必須採用 Base64URL 編碼,才能進行 JSON 傳輸。在前端,剖析 JSON 後,請使用 PublicKeyCredential.parseRequestOptionsFromJSON()
將物件 (包括解碼 Base64URL 字串) 轉換為 navigator.credentials.get()
預期的格式。
以下程式碼片段說明如何擷取及解碼使用密碼金鑰驗證所需的資訊。
// Fetch an encoded PubicKeyCredentialRequestOptions from the server.
const _options = await fetch('/webauthn/signinRequest');
// Deserialize and decode the PublicKeyCredentialRequestOptions.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseRequestOptionsFromJSON(decoded_options);
...
使用 conditional
標記呼叫 WebAuthn API,以便驗證使用者
準備好 publicKeyCredentialRequestOptions
物件 (在下方程式碼範例中稱為 options
) 後,請呼叫 navigator.credentials.get()
來啟動條件式密碼金鑰驗證。
// To abort a WebAuthn call, instantiate an AbortController.
const abortController = new AbortController();
// Invoke WebAuthn to authenticate with a passkey.
const credential = await navigator.credentials.get({
publicKey: options,
signal: abortController.signal,
// Specify 'conditional' to activate conditional UI
mediation: 'conditional'
});
此呼叫的必要參數:
publicKey
:此處必須是您從伺服器擷取並在先前步驟中處理的publicKeyCredentialRequestOptions
物件 (在範例中為options
)。signal
:傳遞AbortController
的信號 (例如abortController.signal
),即可透過程式碼取消get()
要求。如要叫用其他 WebAuthn 呼叫,這項功能就很實用。mediation: 'conditional'
:這是讓 WebAuthn 呼叫具條件的關鍵標記。這會指示瀏覽器等待使用者與自動填入提示互動,而非立即顯示模態對話方塊。
將傳回的公開金鑰憑證傳送至 RP 伺服器
如果使用者選取密碼金鑰並成功驗證身分 (例如使用裝置螢幕鎖定),navigator.credentials.get()
承諾就會解析。這會將 PublicKeyCredential
物件傳回至前端。
有幾個原因可能會導致承諾遭到拒絕。您應在程式碼中處理這些錯誤,方法是檢查 Error
物件的 name
屬性:
NotAllowedError
:使用者已取消操作,或未選取密碼金鑰。AbortError
:作業已中止,可能是因為程式碼使用AbortController
所致。- 其他例外狀況:發生未預期的錯誤。瀏覽器通常會向使用者顯示錯誤對話方塊。
PublicKeyCredential
物件包含多個屬性。與驗證相關的重要屬性包括:
id
:經驗證密碼金鑰憑證的 base64url 編碼 ID。rawId
:憑證 ID 的 ArrayBuffer 版本。response.clientDataJSON
:用戶端資料的 ArrayBuffer。這個欄位包含多項資訊,例如驗證問題和伺服器必須驗證的來源。response.authenticatorData
:驗證器資料的 ArrayBuffer。這個欄位包含 RP ID 等資訊。response.signature
:包含簽名的 ArrayBuffer。這個值是憑證的核心元素,您的伺服器必須使用憑證的已儲存公開金鑰驗證這個簽章。response.userHandle
:ArrayBuffer,其中包含在密碼金鑰註冊期間提供的使用者 ID。authenticatorAttachment
:指出驗證器是用戶端裝置 (platform
) 的一部分,還是外部 (cross-platform
)。如果 使用者透過手機登入,就會產生cross-platform
附件。在這種情況下,建議您提醒使用者在目前的裝置上建立 apasskey,以利日後使用。type
:這個欄位一律設為"public-key"
。
如要將此 PublicKeyCredential
物件傳送至後端,請先呼叫 .toJSON()
方法。這個方法會建立可序列化為 JSON 的憑證版本,可正確處理 ArrayBuffer
屬性 (例如 rawId
、clientDataJSON
、authenticatorData
、signature
和 userHandle
) 轉換為 Base64URL 編碼字串。接著,使用 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/signinResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
驗證簽名
後端伺服器收到公開金鑰憑證時,必須驗證其真實性。這些行為包括:
- 剖析憑證資料。
- 查詢與憑證
id
相關聯的已儲存公開金鑰。 - 根據儲存的公開金鑰驗證收到的
signature
。 - 驗證其他資料,例如驗證碼和來源。
建議您使用伺服器端 FIDO/WebAuthn 程式庫,安全地處理這些加密編譯作業。您可以在 awesome-webauthn GitHub 存放區中找到開放原始碼程式庫。
如果簽章和所有其他斷言皆有效,伺服器就能讓使用者登入。如需詳細的伺服器端驗證步驟,請參閱「伺服器端密碼金鑰驗證」
在後端找不到相符的憑證時,會傳送信號
如果後端伺服器在登入期間找不到 ID 相符的憑證,使用者可能先前已從伺服器中刪除此密碼金鑰,但未從密碼金鑰提供者中刪除。如果密碼金鑰提供者持續建議使用者採用不再適用於網站的密碼金鑰,這類不相符的情況可能會導致使用者體驗混亂。為改善這個問題,您應傳送訊號給密碼金鑰供應器,要求移除孤立的密碼金鑰。
您可以使用 PublicKeyCredential.signalUnknownCredential()
方法 (屬於 Webauthn Signal API),通知密碼金鑰提供者指定的憑證已遭移除或不存在。如果伺服器指出 (例如,使用特定的 HTTP 狀態碼,例如 404) 所提交的憑證 ID 不明,請在用戶端端呼叫這個靜態方法。請將 RP ID 和不明憑證 ID 提供給這個方法。如果密碼金鑰提供者支援信號,則應移除密碼金鑰。
// 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.
...
}
}
驗證後
視使用者登入的方式而定,我們建議您採用不同的流程。
如果使用者未使用密碼金鑰登入
如果使用者未使用密碼金鑰登入您的網站,表示他們可能沒有為該帳戶或目前裝置註冊密碼金鑰。這是鼓勵使用者建立密碼金鑰的好時機。請考慮採用下列做法:
- 將密碼升級為密碼金鑰:使用有條件建立功能,這是 WebAuthn 功能,可讓瀏覽器在使用者成功登入密碼後,自動為使用者建立密碼金鑰。這可簡化密碼金鑰建立程序,大幅提升密碼金鑰的採用率。如要瞭解這項功能的運作方式和導入方式,請參閱「協助使用者更順暢地採用密碼金鑰」一文。
- 手動提示建立密碼金鑰:鼓勵使用者建立密碼金鑰。在使用者完成較複雜的登入程序 (例如多重驗證) 後,這項功能就會生效。不過,請避免過度使用提示,以免影響使用者體驗。」
如要瞭解如何鼓勵使用者建立密碼金鑰,以及其他最佳做法,請參閱「向使用者說明密碼金鑰」一文中的範例。
使用者是否已使用密碼金鑰登入
使用者成功使用密碼金鑰登入後,您可以透過多種方式進一步改善使用體驗,並維持帳戶一致性。
鼓勵使用者在跨裝置驗證後建立新的密碼金鑰
如果使用者使用跨裝置機制 (例如使用手機掃描 QR code) 登入帳戶,所使用的密碼金鑰可能不會儲存在登入裝置的本機上。造成這種情況的可能原因如下:
- 他們有密碼金鑰,但密碼金鑰提供者不支援登入作業系統或瀏覽器。
- 使用者無法存取登入裝置上的密碼金鑰提供者,但仍可在其他裝置上使用密碼金鑰。
在這種情況下,建議您提示使用者在目前的裝置上建立新的密碼金鑰。這樣一來,使用者日後就不必重複進行跨裝置登入程序。如要判斷使用者是否已使用跨裝置密碼金鑰登入,請檢查憑證的 authenticatorAttachment
屬性。如果值為 "cross-platform"
,表示跨裝置驗證。如果是,請說明建立新密碼金鑰的便利性,並引導他們完成建立程序。
使用信號與供應商同步處理密碼金鑰詳細資料
為確保一致性和提供更優質的使用者體驗,依賴方 (RP) 可以使用 WebAuthn Signals API,將憑證和使用者資訊的更新內容傳達給密碼金鑰提供者。
舉例來說,如要確保密碼金鑰提供者的使用者密碼金鑰清單準確無誤,請確保後端的憑證保持同步。您可以表示密碼金鑰已不存在,讓密碼金鑰提供者移除不必要的密碼金鑰。
同樣地,您可以傳送信號,指出使用者是否在您的服務中更新使用者名稱或顯示名稱,以便密碼金鑰供應器 (例如在帳戶選取對話方塊中) 顯示的使用者資訊保持最新狀態。
如要進一步瞭解如何保持密碼金鑰一致,請參閱「使用 Signal API 讓密碼金鑰與伺服器上的憑證保持一致」。
不要要求使用次要驗證方式
密碼金鑰內建完善的防護機制,可防範網路釣魚等常見威脅。因此,第二個驗證因素並不會帶來顯著的安全價值。反而會在使用者登入時造成不必要的步驟。
檢查清單
- 允許使用者透過表單自動填入功能,以密碼金鑰登入。
- 當後端找不到密碼金鑰的對應憑證時,會傳送信號。
- 如果使用者在登入後尚未建立密碼金鑰,請提示他們手動建立密碼金鑰。
- 在使用者使用密碼 (和第二因素) 登入後,自動建立密碼金鑰 (依條件建立)。
- 如果使用者已使用跨裝置密碼金鑰登入,系統會提示建立本機密碼金鑰。
- 在登入或發生變更時,向提供者傳送可用密碼金鑰清單和更新的使用者詳細資料 (使用者名稱、顯示名稱)。