雖然密碼金鑰等 FIDO 憑證旨在取代密碼,但大多數的 FIDO 憑證也可以讓使用者不必輸入使用者名稱。這樣一來,使用者就能從目前網站的密碼金鑰清單中選取帳戶,進行驗證。
早期版本的安全金鑰是設計用於兩步驟驗證方法,因此需要輸入可能的憑證 ID,安全金鑰在不知道憑證 ID 的情況下,也能找到的憑證稱為可探索憑證。目前建立的大多數 FIDO 憑證都是可偵測的憑證,尤其是儲存在密碼管理工具或新式安全金鑰中的密碼金鑰。
為確保憑證會以密碼金鑰 (可偵測的憑證) 形式建立,請在建立憑證時指定 residentKey
和 requireResidentKey
。
依賴方 (RP) 可以在密碼金鑰驗證期間省略 allowCredentials
,以便使用可偵測的憑證。在這種情況下,瀏覽器或系統會向使用者顯示可用密碼金鑰清單,並在建立時透過 user.name
屬性集合識別。如果使用者選取其中一個,user.id
值就會納入產生的簽章中。接著,伺服器可以使用該 ID 或傳回的憑證 ID 來查詢帳戶,而非輸入的使用者名稱。
帳戶選取器 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。
相反地,密碼金鑰的選取作業可與儲存的使用者名稱和密碼一併,整合至傳統登入表單中的欄位自動填入提示。這樣一來,如果使用者有密碼金鑰,就能選取密碼金鑰來「填寫」登入表單;如果使用者有儲存的使用者名稱/密碼組合,就能選取這些項目;如果使用者沒有這兩者,仍可輸入使用者名稱和密碼。
如果 RP 正在進行遷移作業,且使用者同時使用密碼和密碼金鑰,這類使用者體驗最為理想。
如要提供這種使用者體驗,除了將空陣列傳遞至 allowCredentials
屬性或省略參數,還必須在 navigator.credentials.get()
上指定 mediation: 'conditional'
,並在 HTML username
輸入欄位中加上 autocomplete="username webauthn"
註解,或在 password
輸入欄位中加上 autocomplete="password webauthn"
註解。
呼叫 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="username webauthn" ...>
如要瞭解如何打造這類使用者體驗,請參閱「透過表單自動填入功能使用密碼金鑰登入」和「在網路應用程式中使用表單自動填入功能實作密碼金鑰」程式碼研究室。
重新驗證
在某些情況下 (例如使用密碼金鑰重新驗證時),使用者的 ID 已知。在這種情況下,我們希望使用密碼金鑰,但不讓瀏覽器或作業系統顯示任何形式的帳戶選取器。您可以透過在 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 可以提供以下登入體驗:
- 顯示模式帳戶選取器。
- 顯示密碼金鑰表單自動填入功能。
- 重新驗證。
請謹慎使用可供搜尋的憑證。這樣一來,您就能設計出精緻的密碼金鑰登入體驗,讓使用者享有流暢的體驗,並提高使用者參與意願。