雖然 FIDO 憑證 (例如密碼金鑰) 的目標是取代密碼,但大多數憑證也能讓使用者不必輸入使用者名稱。這樣一來,使用者就能從目前網站的密碼金鑰清單中選取帳戶,進行驗證。
早期版本的安全金鑰是兩步驟驗證方式,需要潛在憑證的 ID,因此需要輸入使用者名稱。如果安全金鑰不必知道憑證 ID 就能找到憑證,這類憑證稱為可偵測憑證。目前建立的大多數 FIDO 憑證都是可探索憑證,尤其是儲存在密碼管理工具或新式安全金鑰中的密碼金鑰。
為確保憑證建立為密碼金鑰 (可偵測憑證),請在建立憑證時指定 residentKey 和 requireResidentKey。
在密碼金鑰驗證期間,仰賴方 (RP) 可以省略 allowCredentials,使用可探索憑證。在這種情況下,瀏覽器或系統會向使用者顯示可用的密碼金鑰清單,並以建立時設定的 user.name 屬性識別。如果使用者選取其中一個,user.id 值就會包含在產生的簽章中。伺服器接著可以使用該值或傳回的憑證 ID 查詢帳戶,而不必輸入使用者名稱。
帳戶選取器 UI (例如先前討論的 UI) 絕不會顯示不可探索的憑證。
requireResidentKey和residentKey
如要建立密碼金鑰,請在 navigator.credentials.create() 上指定 authenticatorSelection.residentKey 和 authenticatorSelection.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 第 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。
相反地,密碼金鑰選項可能會與已儲存的使用者名稱和密碼,一起收合在傳統登入表單欄位的自動填入提示中。這樣一來,擁有密碼金鑰的使用者只要選取密碼金鑰,就能「填寫」登入表單;擁有已儲存使用者名稱/密碼組合的使用者可以選取這些組合,而兩者皆無的使用者仍可輸入使用者名稱和密碼。
如果依賴方正在遷移,且同時使用密碼和密碼金鑰,這種使用者體驗最理想。
如要達到這種使用者體驗,除了將空白陣列傳遞至 allowCredentials 屬性或省略參數外,請在 navigator.credentials.get() 上指定 mediation: 'conditional',並使用 autocomplete="username webauthn" 註解 HTML username 輸入欄位,或使用 autocomplete="password webauthn" 註解 password 輸入欄位。
呼叫 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=&qu>ot;username webauthn" ...
如要瞭解如何建構這項使用者體驗,請參閱「透過表單自動填入功能使用密碼金鑰登入」一文,以及「在網頁應用程式中透過表單自動填入功能導入密碼金鑰」程式碼實驗室。
重新驗證
在某些情況下 (例如使用密碼金鑰重新驗證),系統已知使用者 ID。在這種情況下,我們希望使用密碼金鑰,而不讓瀏覽器或 OS 顯示任何形式的帳戶選取器。只要在 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()的結果。
摘要
可探索憑證可讓使用者略過輸入使用者名稱的步驟,大幅提升密碼金鑰登入體驗。結合 residentKey、requireResidentKey 和 allowCredentials,RP 可提供以下登入體驗:
- 顯示帳戶選取器。
- 顯示密碼金鑰表單自動填入功能。
- 重新驗證。
請謹慎使用可探索的憑證。這樣一來,您就能設計出精緻的密碼金鑰登入體驗,讓使用者感到流暢,並更願意參與。