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