Web 身份驗證擴充套件

Web 身份驗證 API 擁有一套擴充套件系統——在憑據建立 (navigator.credentials.create()) 或身份驗證 (navigator.credentials.get()) 操作期間可請求的額外功能。本文介紹瞭如何請求 WebAuthn 擴充套件、檢索有關這些請求響應的資訊以及可用的擴充套件——包括瀏覽器支援和預期的輸入和輸出。

如何使用 WebAuthn 擴充套件

當呼叫 navigator.credentials.create()navigator.credentials.get() 時,啟動 WebAuthn 流程所需的 publicKey 物件引數可以包含一個 extensions 屬性。extensions 的值本身是一個物件,其屬性是依賴方希望在其呼叫的方法中使用任何擴充套件的輸入值。

在後臺,輸入由使用者代理和/或身份驗證器處理。

例如,在 create() 呼叫的 publicKey 物件中,我們可能希望請求使用兩個擴充套件

  1. credProps 擴充套件。依賴方設定 credProps 以請求瀏覽器告知依賴方憑據在註冊後是否是常駐/可發現的。當使用 publicKey.authenticatorSelection.residentKey = "preferred" 呼叫 create() 時,這很有用。要請求它,您還需要在瀏覽器建立憑據時設定 publicKey.extensions.credProps = true,並且根據使用的身份驗證器型別,它將是可發現的(例如,FIDO2 身份驗證器通常會使其可發現;FIDO1/U2F 安全金鑰將是不可發現的)。credProps 僅由使用者代理處理。
  2. minPinLength 擴充套件允許依賴方請求身份驗證器的最小 PIN 長度。這要求 extensions.minPinLength 設定為 trueminPinLength 由身份驗證器處理,使用者代理僅用於將輸入資料傳遞給它。
js
const 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 }],
  authenticatorSelection: {
    residentKey: "preferred",
  },
  extensions: {
    credProps: true,
    minPinLength: true,
  },
};

然後我們可以將 publicKey 物件傳遞給 create() 呼叫以啟動憑據建立流程

js
navigator.credentials.create({ publicKey });

檢索擴充套件請求結果

如果成功,create() 呼叫將返回一個 Promise,它將解析為一個 PublicKeyCredential 物件。一旦擴充套件處理完成,處理結果將在響應中傳達(儘管並非在所有情況下都如此——擴充套件可能沒有輸出)。

js
navigator.credentials
  .create({ publicKey })
  .then((publicKeyCred) => {
    const myClientExtResults = publicKeyCred.getClientExtensionResults();
    // myClientExtResults will contain the output of processing
    // the "credProps" extension

    const authData = publicKeyCred.response.getAuthenticatorData();
    // authData will contain authenticator data, which will include
    // authenticator extension processing results, i.e., minPinLength
  })
  .catch((err) => {
    console.error(err);
  });

如上述程式碼片段所示,有兩種不同的地方可以找到輸出擴充套件結果

  1. 您可以透過呼叫 PublicKeyCredential.getClientExtensionResults() 方法找到客戶端(使用者代理)擴充套件處理的結果。這會返回一個 map,其中每個條目都以擴充套件識別符號字串作為鍵,以客戶端處理擴充套件的輸出作為值。在上面的示例中,如果瀏覽器支援 credProps 擴充套件並且它被正確處理,myClientExtResults map 物件將包含一個條目 "credProps",其值為 { rk: true }。這將驗證所建立的憑據確實是可發現的。

  2. 您可以在操作的身份驗證器資料中找到身份驗證器擴充套件處理的結果

    身份驗證器資料採用具有一致結構的 ArrayBuffer 形式——參見身份驗證器資料。身份驗證器擴充套件結果資料始終位於末尾的一個部分,作為一個表示結果的 CBOR 對映。有關完整身份驗證器資料結構的詳細描述,請參閱 AuthenticatorAssertionResponse.authenticatorData

    回到我們的示例,如果依賴方被授權接收 minPinLength 值,身份驗證器資料將包含以下形式的表示:"minPinLength": uint

可用擴充套件

下面的擴充套件並不代表所有可用擴充套件的詳盡列表。我們選擇記錄我們已知已標準化並至少被一個渲染引擎支援的擴充套件。

appid

允許依賴方請求先前使用舊版 FIDO U2F JavaScript API 註冊的憑據的斷言,避免重新註冊憑據的麻煩。appid 是該 API 中與 WebAuthn 中的 rpId 等效的(儘管請記住,appid 採用 URL 形式,而 rpId 採用域名形式)。

輸入

publicKeyextensions 屬性必須包含一個 appid 屬性,其值是舊版 API 中使用的應用程式識別符號。例如

js
({
  extensions: {
    appid: "https://accounts.example.com",
  },
});

您還必須在 publicKeyallowCredentials 屬性中列出 FIDO U2F 憑據 ID,例如

js
({
  allowCredentials: [
    {
      id: arrayBuffer, // needs to contain decoded binary form of id
      transports: ["nfc", "usb"],
      type: "public-key",
    },
  ],
});

輸出

如果 appid 成功用於斷言,則輸出 appid: true,否則輸出 appid: false

appidExclude

允許依賴方在註冊期間排除包含先前使用舊版 FIDO U2F JavaScript API 註冊的指定憑據的身份驗證器。這是必需的,因為預設情況下 excludeCredentials 欄位的內容被假定為 WebAuthn 憑據。使用此擴充套件時,您可以在 excludeCredentials 中包含舊版 FIDO U2F 憑據,它們將被識別為舊版憑據。

輸入

publicKeyextensions 屬性必須包含一個 appidExclude 屬性,其值是請求按舊版 FIDO U2F 憑據排除身份驗證器的依賴方識別符號。例如

js
({
  extensions: {
    appidExclude: "https://accounts.example.com",
  },
});

然後您可以在 publicKeyexcludeCredentials 屬性中列出 FIDO U2F 憑據,例如

js
({
  excludeCredentials: [
    {
      id: arrayBuffer, // needs to contain decoded binary form of id
      transports: ["nfc", "usb"],
      type: "public-key",
    },
  ],
});

輸出

如果擴充套件已執行,則輸出 appidExclude: true,否則輸出 appidExclude: false

credProps

允許依賴方請求有關已建立憑據的附加資訊/屬性。目前,這僅在呼叫 create() 並將 publicKey.authenticatorSelection.residentKey 設定為 "preferred" 時有用;它請求有關已建立憑據是否可發現的資訊。

輸入

publicKeyextensions 屬性必須包含一個值為 truecredProps 屬性

js
({
  extensions: {
    credProps: true,
  },
});

您還必須將 authenticatorSelection.requireResidentKey 設定為 true,這表示需要常駐金鑰。

js
({
  authenticatorSelection: {
    requireResidentKey: true,
  },
});

輸出

如果註冊的 PublicKeyCredential 是客戶端可發現憑據,則輸出以下內容

js
({
  credProps: {
    rk: true,
  },
});

如果輸出中 rk 設定為 false,則憑據是伺服器端憑據。如果輸出中沒有 rk,則不知道憑據是客戶端可發現的還是伺服器端的。

credProtect

允許依賴方在建立憑據時指定最低憑據保護策略。

輸入

publicKeyextensions 屬性必須包含一個 credentialProtectionPolicy 屬性,用於指定要建立憑據的保護級別,以及一個布林 enforceCredentialProtectionPolicy 屬性,用於指定 create() 呼叫是否應失敗而不是建立不符合指定策略的憑據

js
({
  extensions: {
    credentialProtectionPolicy: "userVerificationOptional",
    enforceCredentialProtectionPolicy: true,
  },
});

可用的 credentialProtectionPolicy 值如下

"userVerificationOptional" 實驗性功能

使用者驗證是可選的。傳送給身份驗證器進行處理的等效 credProtect 值為 0x01

"userVerificationOptionalWithCredentialIDList"

僅當憑據可發現(即,它是客戶端可發現的)時,使用者驗證才是可選的。傳送給身份驗證器進行處理的等效 credProtect 值為 0x02

"userVerificationRequired"

始終需要使用者驗證。傳送給身份驗證器進行處理的等效 credProtect 值為 0x03

注意:Chromium 將預設使用 userVerificationOptionalWithCredentialIDListuserVerificationRequired,具體取決於請求型別

  • 當建立憑據時,如果 residentKey 設定為 preferredrequired,Chromium 將請求保護級別為 userVerificationOptionalWithCredentialIDList。(設定 requireResidentKeyrequired 視為相同。)這確保了簡單地擁有安全金鑰不會允許查詢給定 rpId 的可發現憑據的存在。
  • 此外,如果 residentKeyrequireduserVerificationpreferred,則保護級別將提高到 userVerificationRequired。這確保了擁有安全金鑰不會允許登入到不需要使用者驗證的網站。(這不是完全保護;網站仍應仔細考慮其使用者的安全性。)
  • 如果網站請求明確的 credProtect 級別,則該級別將覆蓋這些預設值。如果安全金鑰的預設級別更高,這些預設值絕不會導致保護級別低於安全金鑰的預設級別。

假設 enforceCredentialProtectionPolicy 值為 true。在這種情況下,如果無法遵守策略(例如,它需要使用者驗證,但身份驗證器不支援使用者驗證),則 create() 呼叫將失敗。如果為 false,系統將盡最大努力建立符合策略的憑據,但如果不可能,它仍將盡可能建立符合策略的憑據。

輸出

如果 create() 呼叫成功,身份驗證器資料將包含以下形式的 credProtect 值表示,該值表示所設定的策略

js
({ credProtect: 0x01 });

largeBlob

允許依賴方在身份驗證器上儲存與憑據關聯的 blob——例如,它可能希望直接儲存證書而不是執行集中式身份驗證服務。

輸入

create() 呼叫期間,publicKeyextensions 屬性必須包含一個具有以下物件結構的 largeBlob 屬性

js
({
  extensions: {
    largeBlob: {
      support: "required",
    },
  },
});

support 屬性的值是一個字串,可以是以下之一

  • "preferred":如果可能,憑據將使用可以儲存 blob 的身份驗證器建立,但如果不能,它仍將建立一個。輸出的“supported”屬性報告身份驗證器儲存 blob 的能力。
  • "required":憑據將使用身份驗證器建立以儲存 blob。如果無法做到這一點,create() 呼叫將失敗。

get() 呼叫期間,publicKeyextensions 屬性必須包含一個具有兩個子屬性之一——readwritelargeBlob 屬性(如果兩者都存在,get() 將失敗)

read 屬性是一個布林值。值為 true 表示依賴方希望獲取與斷言憑據關聯的先前寫入的 blob

js
({
  extensions: {
    largeBlob: {
      read: true,
    },
  },
});

write 屬性的值是 ArrayBufferTypedArrayDataView,表示依賴方希望與現有憑據一起儲存的 blob

js
({
  extensions: {
    largeBlob: {
      write: arrayBuffer,
    },
  },
});

注意:要使寫入身份驗證操作成功,publicKey.allowCredentials 必須只包含一個表示您希望 blob 儲存在其旁邊的憑據的元素。

輸出

如果註冊的憑據能夠儲存 blob,則成功的 create() 呼叫會提供以下擴充套件輸出

js
({
  largeBlob: {
    supported: true, // false if it cannot store blobs
  },
});

如果成功,get() 讀取呼叫會將 blob 作為 ArrayBuffer 在擴充套件輸出中提供

js
({
  largeBlob: {
    blob: arrayBuffer,
  },
});

注意:如果失敗,將返回 largeBlob 物件,但不會出現 blob

get() 寫入呼叫透過擴充套件輸出中的 written 布林值指示寫入操作是否成功。true 值表示已成功寫入關聯的身份驗證器,false 表示不成功。

js
({
  largeBlob: {
    written: true,
  },
});

minPinLength

允許依賴方請求身份驗證器的最小 PIN 長度。

輸入

publicKeyextensions 屬性必須包含一個值為 trueminPinLength 屬性

js
({
  extensions: {
    minPinLength: true,
  },
});

輸出

如果依賴方被授權接收 minPinLength 值(如果其 rpId 存在於身份驗證器的授權依賴方列表中),則身份驗證器資料將包含以下形式的表示

js
({ minPinLength: uint });

如果依賴方未授權,則擴充套件將被忽略,並且不提供 "minPinLength" 輸出值。

payment

允許依賴方請求建立可用於安全支付確認的 WebAuthn 憑據——由依賴方和其他方共同使用;請參閱使用安全支付確認

輸入

payment 擴充套件的輸入在 AuthenticationExtensionsPaymentInputs 字典中定義

isPayment

一個布林值,指示擴充套件是否處於活動狀態。

rpID

正在使用的憑據的依賴方 ID。僅在身份驗證時使用;不用於註冊。

topOrigin

頂級框架的來源。僅在身份驗證時使用;不用於註冊。

payeeName

如果存在,顯示給使用者的收款人名稱。僅在身份驗證時使用;不用於註冊。

payeeOrigin

如果存在,顯示給使用者的收款人來源。僅在身份驗證時使用;不用於註冊。

total

顯示給使用者的交易金額。僅在身份驗證時使用;不用於註冊。總額型別為 PaymentCurrencyAmount

instrument

顯示給使用者的工具詳細資訊。僅在身份驗證時使用;不用於註冊。工具型別為 PaymentCredentialInstrument

輸出

None

prf

允許依賴方從與憑據關聯的偽隨機函式 (PRF) 獲取一個或兩個輸入的輸出。PRF 實際上是一個隨機預言機——一個對任何給定輸入返回隨機值,但對相同輸入總是返回相同值的函式。

生成與使用者憑據關聯的隨機數的能力在許多加密應用程式中都很有用。例如,它可以用於生成對稱金鑰以加密敏感資料,並且只能由擁有種子和關聯身份驗證器的使用者解密。它也可以類似地用於建立端到端加密的對稱金鑰,該金鑰使用來自伺服器的值作為種子,並且對於該憑據和會話是唯一的。

該擴充套件允許您將 ArrayBufferTypedArray 型別的緩衝區值傳遞給身份驗證器,身份驗證器將返回使用關聯憑據的 PRF 評估該值的結果。這可以在斷言中完成,作為身份驗證工作流的一部分——指定要評估結果的一個或多個憑據。它也可以在建立憑據時完成;但支援在建立憑據時生成輸出的身份驗證器較少。

輸入

create() 呼叫期間,publicKeyextensions 屬性可能包含一個 prf 屬性,該屬性具有 eval 物件,其中包含 first 屬性和可選的 second 屬性。這些屬性是 ArrayBufferTypedArray 例項,其中包含要傳遞給憑據的 PRF 的值。

例如,以下定義可以在建立新憑據時使用,以便從伺服器提供的秘密建立新的對稱金鑰。

js
({
  extensions: {
    prf: {
      eval: { first: new TextEncoder().encode("Salt for new symmetric key") },
    },
  },
});

如果需要為憑據建立兩個隨機值,例如在每次會話旋轉加密金鑰的工作流中,可以使用可選的 second 屬性。例如,在此類工作流中,在每個會話中您傳遞兩個鹽值:first 鹽值返回一個可用於解密上一會話資料的值,而 second 鹽值返回一個可用於加密此會話資料的值。在後續會話中,second 鹽值移動到 first 鹽值的位置,從而限制了特定鹽值可能被有效洩露的生命週期。

js
({
  extensions: {
    prf: {
      eval: {
        first: currentSessionKey, // salt for current session
        second: nextSessionKey, // salt for next session
      },
    },
  },
});

create() 呼叫可能因以下異常而拒絕

  • NotSupportedError DomException
    • eval 物件中存在 evalByCredential 鍵。

請注意,在建立憑據時評估 PRF 可能不受支援,並且這將在輸出中報告。您仍然可以嘗試在斷言中評估 PRF,如下所示。

get() 呼叫期間,publicKeyextensions 屬性可能包含一個帶有 evalByCredential 子屬性的 prf 屬性。這是一個物件,將 Base64 URL 編碼的憑據 ID 對映到與上述相同形式的評估物件。換句話說,這允許您指定要評估不同憑據的值。

js
({
  extensions: {
    prf: {
      evalByCredential: {
        "<credentialId>": { first: bufferOne, second: bufferTwo },
        // …
        "<credentialId2>": {
          first: anotherBufferOne,
          second: anotherBufferTwo,
        },
      },
    },
  },
});

get() 呼叫可能因以下異常而拒絕

NotSupportedError DomException

如果 evalprf 物件,或者當 evalByCredential 不為空時 allowCredentials 為空。

SyntaxError DomException

evalByCredential 中的任何鍵都是空字串或不是有效的 Base64 URL 編碼,或者與 publicKey.allowCredentials 中的某個元素的 ID 不匹配。

輸出

如果註冊的憑據支援在建立憑據時使用 PRF,則成功的 create() 呼叫會提供以下擴充套件輸出。

js
({
  prf: {
    enabled: true, // PRF can be used when creating credentials.
    results: { first: outputBuffer1, second: outputBuffer2 },
  },
});

enabled 屬性指示在建立憑據時是否可以使用 PRF。firstsecond 屬性包含評估輸入上的 firstsecond 的結果,如果未指定相應的輸入,則將省略 second

如果身份驗證器不支援在建立時使用 PRF,則 create() 的輸出將如下所示

js
({
  prf: {
    enabled: false, // PRF cannot be used when creating credentials.
  },
});

get() 返回與 create() 相同的 prf 物件,具有相同的結構,只是它省略了 enabled 鍵。該物件包含與使用者選擇的憑據的輸入相對應的 PRF 值。

js
({
  prf: {
    results: { first: outputBuffer1, second: outputBuffer2 },
  },
});

請注意,enabled 僅作為 create() 的輸出存在,並指示在建立憑據時身份驗證器是否支援 PRF。如果身份驗證器根本不支援 PRF,則 get() 呼叫的結果將是

js
({
  prf: {},
});

規範

規範
Web Authentication:訪問公鑰憑證的 API - 第 3 級
# sctn-defined-extensions
未知規範
# sctn-defined-extensions

WebAuthn 擴充套件在許多地方都有指定。IANA 的 WebAuthn 擴充套件識別符號提供了所有擴充套件的登錄檔,但請記住,有些可能已被棄用。

瀏覽器相容性

WebAuthn 擴充套件的相容性資料已分解為兩個表——可在憑據註冊 (create()) 期間使用的擴充套件,以及可在身份驗證 (get()) 期間使用的擴充套件。某些擴充套件在這兩種操作期間均可使用。

api.CredentialsContainer.create.publicKey_option.extensions

api.CredentialsContainer.get.publicKey_option.extensions