Web Authentication API
Baseline 廣泛可用 *
Web 身份驗證 API (WebAuthn) 是憑據管理 API 的擴充套件,它透過公鑰加密實現強大的身份驗證,支援無密碼身份驗證和無需簡訊的安全多因素身份驗證 (MFA)。
WebAuthn 概念和用法
WebAuthn 使用非對稱(公鑰)加密而不是密碼或簡訊進行網站註冊、身份驗證和多因素身份驗證。這帶來了一些好處:
- 防範網路釣魚: 攻擊者建立虛假登入網站也無法以使用者身份登入,因為簽名會隨網站的源而改變。
- 減少資料洩露的影響: 開發人員無需對公鑰進行雜湊處理,如果攻擊者獲得了用於驗證身份的公鑰,他們也無法進行身份驗證,因為他們需要私鑰。
- 不受密碼攻擊的影響: 有些使用者可能會重複使用密碼,攻擊者可能會獲取使用者在其他網站上的密碼(例如,透過資料洩露)。此外,文字密碼比數字簽名更容易被暴力破解。
許多網站已經有允許使用者註冊新賬戶或登入現有賬戶的頁面,而 WebAuthn 作為系統身份驗證部分的替代或增強。它擴充套件了憑據管理 API,抽象了使用者代理和身份驗證器之間的通訊,並提供了以下新功能:
- 當
navigator.credentials.create()與publicKey選項一起使用時,使用者代理透過身份驗證器建立新憑據——用於註冊新賬戶或將新的非對稱金鑰對與現有賬戶關聯。- 註冊新賬戶時,這些憑據儲存在伺服器上(也稱為服務或信賴方),後續可用於使用者登入。
- 非對稱金鑰對儲存在身份驗證器中,然後可用於在 MFA 期間對信賴方進行使用者身份驗證。身份驗證器可以嵌入到使用者代理中,嵌入到作業系統中(例如 Windows Hello),也可以是物理令牌(例如 USB 或藍牙安全金鑰)。
- 當
navigator.credentials.get()與publicKey選項一起使用時,使用者代理使用現有憑據集對信賴方進行身份驗證(作為主要登入或在 MFA 期間提供額外的因素,如上所述)。
在最基本的形式中,create() 和 get() 都從伺服器接收一個稱為“挑戰”的非常大的隨機數,並將由私鑰簽名的挑戰返回給伺服器。這向伺服器證明使用者擁有身份驗證所需的私鑰,而不會透過網路洩露任何秘密。
注意:“挑戰”必須是一個至少 16 位元組大小的隨機資訊緩衝區。
建立金鑰對和註冊使用者
為了說明憑據建立過程的工作原理,讓我們描述使用者希望向信賴方註冊憑據時發生的典型流程:
-
信賴方伺服器使用適當的安全機制(例如Fetch或XMLHttpRequest)將使用者和信賴方資訊以及“挑戰”傳送到處理註冊的 Web 應用程式。
注意: 信賴方伺服器和 Web 應用程式之間共享資訊的格式由應用程式決定。推薦的方法是交換JSON 型別表示物件,用於憑據和憑據選項。
PublicKeyCredential中已建立了方便的方法,用於將 JSON 表示轉換為身份驗證 API 所需的形式:parseCreationOptionsFromJSON()、parseRequestOptionsFromJSON()和PublicKeyCredential.toJSON()。 -
Web 應用程式透過呼叫
navigator.credentials.create(),代表信賴方啟動透過身份驗證器生成新憑據的過程。此呼叫會傳入一個publicKey選項,指定裝置功能,例如裝置是否提供自己的使用者身份驗證(例如,使用生物識別)。典型的
create()呼叫可能如下所示:jslet credential = await navigator.credentials.create({ publicKey: { challenge: new Uint8Array([117, 61, 252, 231, 191, 241 /* … */]), rp: { id: "acme.com", name: "ACME Corporation" }, user: { id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]), name: "jamiedoe", displayName: "Jamie Doe", }, pubKeyCredParams: [{ type: "public-key", alg: -7 }], }, });create()呼叫的引數以及經過簽名的 SHA-256 雜湊值會傳遞給身份驗證器,以確保其未被篡改。 -
身份驗證器獲得使用者同意後,會生成一個金鑰對,並將公鑰和可選的簽名證明返回給 Web 應用程式。這會在
create()呼叫返回的Promisefulfilled 時提供,形式為PublicKeyCredential物件例項(PublicKeyCredential.response屬性包含證明資訊)。 -
Web 應用程式再次使用適當的機制將
PublicKeyCredential轉發給信賴方伺服器。 -
信賴方伺服器儲存公鑰,並與使用者身份繫結,以便記住憑據以供將來的身份驗證。在此過程中,它會執行一系列檢查,以確保註冊完成且未被篡改。這些檢查包括:
- 驗證挑戰是否與傳送的挑戰相同。
- 確保來源是預期的來源。
- 驗證簽名和證明是否使用了首次生成金鑰對所用的特定身份驗證器模型的正確證書鏈。
警告: 證明為信賴方提供了一種確定身份驗證器來源的方法。信賴方不應嘗試維護身份驗證器白名單。
使用者身份驗證
使用者使用 WebAuthn 註冊後,即可使用該服務進行身份驗證(登入)。身份驗證流程類似於註冊流程,主要區別在於身份驗證:
- 不需要使用者或信賴方資訊
- 使用先前為服務生成的金鑰對建立斷言,而不是身份驗證器的金鑰對。
典型的身份驗證流程如下:
-
信賴方生成一個“挑戰”並使用適當的安全機制將其傳送給使用者代理,同時傳送信賴方和使用者憑據列表。它還可以指示在哪裡查詢憑據,例如,在本地內建身份驗證器上,或透過 USB、BLE 等外部身份驗證器上。
-
瀏覽器請求身份驗證器透過呼叫
navigator.credentials.get()來簽署挑戰,該呼叫將憑據傳遞給publicKey選項。典型的
get()呼叫可能如下所示:jslet credential = await navigator.credentials.get({ publicKey: { challenge: new Uint8Array([139, 66, 181, 87, 7, 203 /* … */]), rpId: "acme.com", allowCredentials: [ { type: "public-key", id: new Uint8Array([64, 66, 25, 78, 168, 226, 174 /* … */]), }, ], userVerification: "required", }, });get()呼叫的引數會傳遞給身份驗證器進行身份驗證。 -
如果身份驗證器包含給定的憑據之一併且能夠成功簽署挑戰,則在收到使用者同意後,它會向 Web 應用程式返回一個簽名的斷言。這在
get()呼叫返回的Promise完成時以PublicKeyCredential物件例項的形式提供(PublicKeyCredential.response屬性包含斷言資訊)。 -
Web 應用程式將簽名的斷言轉發給信賴方伺服器,供信賴方驗證。驗證檢查包括:
- 使用註冊請求期間儲存的公鑰驗證身份驗證器的簽名。
- 確保身份驗證器簽名的挑戰與伺服器生成的挑戰匹配。
- 檢查信賴方 ID 是否是該服務所預期的 ID。
-
一旦伺服器驗證透過,身份驗證流程就被認為是成功的。
可發現憑據和有條件中介
可發現憑據是從身份驗證器中檢索(由瀏覽器發現)的,當用戶登入信賴方 Web 應用程式時,將其作為登入選項提供。相比之下,不可發現憑據由信賴方伺服器提供,供瀏覽器作為登入選項提供。
可發現憑據 ID 和相關元資料,例如使用者名稱和顯示名稱,儲存在客戶端身份驗證器中,例如瀏覽器密碼管理器、身份驗證應用程式或硬體解決方案(如 YubiKey)。身份驗證器中提供此資訊意味著使用者可以方便地登入,而無需提供憑據,並且信賴方在斷言時無需提供credentialId(儘管如果需要也可以這樣做;如果憑據由 RP 斷言,則遵循不可發現的工作流)。
可發現憑據透過帶有指定residentKey的create()呼叫建立。新憑據的 credentialId、使用者元資料和公鑰由身份驗證器儲存,如上所述,但也會返回給 Web 應用程式並存儲在 RP 伺服器上。
為了進行身份驗證,RP 伺服器呼叫get(),並指定有條件中介,即mediation設定為conditional,一個空的allowCredentials列表(意味著只能顯示可發現憑據)和一個挑戰。
有條件中介會導致在身份驗證器中發現的可發現憑據以非模態 UI 的形式呈現給使用者,並指示請求憑據的來源,而不是模態對話方塊。實際上,這意味著在登入表單中自動填充可用憑據。儲存在可發現憑據中的元資料可以顯示,以幫助使用者在登入時選擇憑據。要在登入表單中顯示可發現憑據,您還需要在表單欄位中包含autocomplete="webauthn"。
重申一下,信賴方不會告訴身份驗證器要向用戶提供哪些憑據——相反,身份驗證器會提供其可用的列表。一旦使用者選擇了憑據,身份驗證器會使用它透過關聯的私鑰對挑戰進行簽名,然後瀏覽器將簽名的挑戰及其 credentialId 返回給 RP 伺服器。
RP 伺服器上的後續身份驗證過程與不可發現憑據的相同。
注意: 您可以透過呼叫PublicKeyCredential.isConditionalMediationAvailable()方法來檢查特定使用者代理上是否提供條件中介。
通行金鑰是可發現憑據的一個重要用例;有關實施細節,請參閱為無密碼登入建立通行金鑰和透過表單自動填充使用通行金鑰登入。有關可發現憑據的更多一般資訊,請參閱可發現憑據深入探究。
當使用條件中介進行身份驗證時,無論其實際值如何,都會將阻止靜默訪問標誌(請參閱CredentialsContainer.preventSilentAccess())視為 true:如果發現適用的憑據,條件行為始終涉及某種形式的使用者中介。
注意: 如果沒有發現憑據,則不會顯示非模態對話方塊,並且使用者代理可能會以取決於憑據型別的方式提示使用者採取行動(例如,插入包含憑據的裝置)。
可發現憑據同步方法
使用者身份驗證器中儲存的關於可發現憑據的資訊可能會與信賴方伺服器不同步。當用戶在 RP Web 應用程式上刪除憑據或修改其使用者/顯示名稱而未更新身份驗證器時,可能會發生這種情況。
API 提供了方法,允許信賴方伺服器向身份驗證器傳送更改訊號,以便它可以更新其儲存的憑據:
PublicKeyCredential.signalAllAcceptedCredentials():向身份驗證器傳送訊號,告知 RP 伺服器仍為特定使用者持有的所有有效憑據 ID。PublicKeyCredential.signalCurrentUserDetails():向身份驗證器傳送訊號,告知特定使用者已在 RP 伺服器上更新了其使用者名稱和/或顯示名稱。PublicKeyCredential.signalUnknownCredential():向身份驗證器傳送訊號,告知 RP 伺服器未識別憑據 ID。
signalUnknownCredential() 和 signalAllAcceptedCredentials() 的目的似乎相似,那麼每種情況應該使用哪一個呢?
signalAllAcceptedCredentials()應在每次成功登入後呼叫,以及當用戶已登入且您希望更新其憑據狀態時呼叫。它只能在使用者經過身份驗證時呼叫,因為它共享給定使用者的所有credentialId列表。如果使用者未經身份驗證,這將導致隱私洩露。signalUnknownCredential()應在登入失敗後呼叫,以向身份驗證器發出訊號,表明所選憑據的credentialId無法驗證,應將其刪除。該方法可以在使用者未經身份驗證時安全呼叫,因為它只將單個credentialId(客戶端剛剛嘗試進行身份驗證的那個)傳遞給身份驗證器,並且不傳遞任何使用者資訊。
根據客戶端功能定製工作流
註冊和登入工作流可以根據 WebAuthn 客戶端(瀏覽器)的功能進行定製。PublicKeyCredential.getClientCapabilities() 靜態方法可用於查詢這些功能;它返回一個物件,其中每個鍵引用一個 WebAuthn 功能或擴充套件,每個值都是一個布林值,指示對該功能的支援。
這可以用於例如檢查:
- 客戶端對各種身份驗證器的支援,例如通行金鑰或生物識別使用者驗證。
- 客戶端是否支援使信賴方和身份驗證器憑據保持同步的方法。
- 客戶端是否允許在具有相同來源的不同網站上使用單個通行金鑰。
下面的程式碼演示瞭如何使用 getClientCapabilities() 來檢查客戶端是否支援提供生物識別使用者驗證的身份驗證器。請注意,實際執行的操作取決於您的站點。對於要求生物識別身份驗證的站點,您可能需要將登入 UI 替換為一條訊息,指示需要生物識別身份驗證,並且使用者應嘗試使用其他瀏覽器或裝置。
async function checkIsUserVerifyingPlatformAuthenticatorAvailable() {
const capabilities = await PublicKeyCredential.getClientCapabilities();
// Check the capability: userVerifyingPlatformAuthenticator
if (capabilities.userVerifyingPlatformAuthenticator) {
// Perform actions if biometric support is available
} else {
// Perform actions if biometric support is not available.
}
}
控制 API 訪問
WebAuthn 的可用性可以透過許可權策略進行控制,具體指定兩個指令:
publickey-credentials-create:控制帶publicKey選項的navigator.credentials.create()的可用性。publickey-credentials-get:控制帶publicKey選項的navigator.credentials.get()的可用性。
這兩個指令的預設允許列表值都是 "self",這意味著預設情況下這些方法可以在頂級文件上下文中使用。此外,get() 可以在從與最頂層文件相同的來源載入的巢狀瀏覽上下文中使用。如果publickey-credentials-get 和publickey-credentials-create Permissions-Policy 指令允許,get() 和 create() 可以在從與最頂層文件不同的來源載入的巢狀瀏覽上下文中使用(即在跨域 中)。對於跨域 create() 呼叫,如果許可權是由iframe 上的 allow= 授予的,則該框架還必須具有瞬時啟用。
注意: 如果策略禁止使用這些方法,它們返回的promise將因 NotAllowedError DOMException 而拒絕。
基本訪問控制
如果您希望僅允許訪問特定子域,您可以這樣提供:
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://subdomain.example.com")
允許在 中嵌入 create 和 get() 呼叫
如果您希望在 中使用 get() 或 create() 進行身份驗證,則需要遵循幾個步驟:
-
嵌入信賴方站點的站點必須透過
allow屬性提供許可權:-
如果使用
get():html<iframe src="https://auth.provider.com" allow="publickey-credentials-get *"> </iframe> -
如果使用
create():html<iframe src="https://auth.provider.com" allow="publickey-credentials-create 'self' https://a.auth.provider.com https://b.auth.provider.com"> </iframe>如果
create()是跨域呼叫,還必須具有瞬時啟用。
-
-
信賴方站點必須透過
Permissions-Policy頭部提供上述訪問許可權:httpPermissions-Policy: publickey-credentials-get=* Permissions-Policy: publickey-credentials-create=*或者只允許特定 URL 在
中嵌入信賴方站點:httpPermissions-Policy: publickey-credentials-get=("https://subdomain.example.com") Permissions-Policy: publickey-credentials-create=("https://*.auth.provider.com")
介面
AuthenticatorAssertionResponse-
向服務提供證據,證明身份驗證器擁有必要的金鑰對,能夠成功處理由
CredentialsContainer.get()呼叫發起的身份驗證請求。在get()Promisefulfilled 時獲得的PublicKeyCredential例項的response屬性中可用。 AuthenticatorAttestationResponse-
WebAuthn 憑證註冊的結果(即
CredentialsContainer.create()呼叫)。它包含伺服器執行 WebAuthn 斷言所需的憑證資訊,例如憑證 ID 和公鑰。在create()Promisefulfilled 時獲得的PublicKeyCredential例項的response屬性中可用。 AuthenticatorResponse-
是
AuthenticatorAttestationResponse和AuthenticatorAssertionResponse的基本介面。 PublicKeyCredential-
提供有關公鑰/私鑰對的資訊,該金鑰對是一種憑證,用於使用防網路釣魚和防資料洩露的非對稱金鑰對(而非密碼)登入服務。當透過
create()或get()呼叫返回的Promisefulfilled 時獲得。
其他介面的擴充套件
CredentialsContainer.create(),publicKey選項-
如上所述,使用
publicKey選項呼叫create()將啟動透過身份驗證器建立新非對稱金鑰憑證的過程。 CredentialsContainer.get(),publicKey選項-
使用
publicKey選項呼叫get()會指示使用者代理使用現有憑據集向信賴方進行身份驗證。
示例
演示站點
- Mozilla 演示網站及其原始碼。
- Google 演示網站及其原始碼。
- WebAuthn.io 演示網站及其原始碼。
- github.com/webauthn-open-source 及其客戶端原始碼和伺服器原始碼
使用示例
// sample arguments for registration
const createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. - Service):
rp: {
name: "Acme",
},
// User:
user: {
id: new Uint8Array(16),
name: "carina.p.anand@example.com",
displayName: "Carina P. Anand",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
],
attestation: "direct",
timeout: 60000,
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x8c, 0x0a, 0x26, 0xff, 0x22, 0x91, 0xc1, 0xe9, 0xb9, 0x4e, 0x2e, 0x17,
0x1a, 0x98, 0x6a, 0x73, 0x71, 0x9d, 0x43, 0x48, 0xd5, 0xa7, 0x6a, 0x15,
0x7e, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0f, 0xef,
]).buffer,
},
};
// sample arguments for login
const getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xda, 0xee, 0xee, 0xb9, 0x94, 0xc3, 0xc2, 0x15,
0x67, 0x65, 0x26, 0x22, 0xe3, 0xf3, 0xab, 0x3b, 0x78, 0x2e, 0xd5, 0x6f,
0x81, 0x26, 0xe2, 0xa6, 0x01, 0x7d, 0x74, 0x50,
]).buffer,
},
};
// register / create a new credential
navigator.credentials
.create(createCredentialDefaultArgs)
.then((cred) => {
console.log("NEW CREDENTIAL", cred);
// normally the credential IDs available for an account would come from a server
// but we can just copy them from above…
const idList = [
{
id: cred.rawId,
transports: ["usb", "nfc", "ble"],
type: "public-key",
},
];
getCredentialDefaultArgs.publicKey.allowCredentials = idList;
return navigator.credentials.get(getCredentialDefaultArgs);
})
.then((assertion) => {
console.log("ASSERTION", assertion);
})
.catch((err) => {
console.log("ERROR", err);
});
規範
| 規範 |
|---|
| Web Authentication:訪問公鑰憑證的 API - 第 3 級 # iface-pkcredential |
瀏覽器相容性
載入中…