Web 身份验证扩展
Web 身份验证 API 拥有一套扩展系统——在凭据创建 (navigator.credentials.create()
) 或身份验证 (navigator.credentials.get()
) 操作期间可请求的额外功能。本文介绍了如何请求 WebAuthn 扩展、检索有关这些请求响应的信息以及可用的扩展——包括浏览器支持和预期的输入和输出。
如何使用 WebAuthn 扩展
当调用 navigator.credentials.create()
或 navigator.credentials.get()
时,启动 WebAuthn 流程所需的 publicKey
对象参数可以包含一个 extensions
属性。extensions
的值本身是一个对象,其属性是依赖方希望在其调用的方法中使用任何扩展的输入值。
在后台,输入由用户代理和/或身份验证器处理。
例如,在 create()
调用的 publicKey
对象中,我们可能希望请求使用两个扩展
credProps
扩展。依赖方设置credProps
以请求浏览器告知依赖方凭据在注册后是否是常驻/可发现的。当使用publicKey.authenticatorSelection.residentKey = "preferred"
调用create()
时,这很有用。要请求它,您还需要在浏览器创建凭据时设置publicKey.extensions.credProps = true
,并且根据使用的身份验证器类型,它将是可发现的(例如,FIDO2 身份验证器通常会使其可发现;FIDO1/U2F 安全密钥将是不可发现的)。credProps
仅由用户代理处理。minPinLength
扩展允许依赖方请求身份验证器的最小 PIN 长度。这要求extensions.minPinLength
设置为true
。minPinLength
由身份验证器处理,用户代理仅用于将输入数据传递给它。
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()
调用以启动凭据创建流程
navigator.credentials.create({ publicKey });
检索扩展请求结果
如果成功,create()
调用将返回一个 Promise
,它将解析为一个 PublicKeyCredential
对象。一旦扩展处理完成,处理结果将在响应中传达(尽管并非在所有情况下都如此——扩展可能没有输出)。
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);
});
如上述代码片段所示,有两种不同的地方可以找到输出扩展结果
-
您可以通过调用
PublicKeyCredential.getClientExtensionResults()
方法找到客户端(用户代理)扩展处理的结果。这会返回一个map
,其中每个条目都以扩展标识符字符串作为键,以客户端处理扩展的输出作为值。在上面的示例中,如果浏览器支持credProps
扩展并且它被正确处理,myClientExtResults
map 对象将包含一个条目"credProps"
,其值为{ rk: true }
。这将验证所创建的凭据确实是可发现的。 -
您可以在操作的身份验证器数据中找到身份验证器扩展处理的结果
- 对于成功
create()
调用返回的PublicKeyCredential
,这可以通过调用publicKeyCredential.response.getAuthenticatorData()
来返回。 - 对于成功
get()
调用返回的PublicKeyCredential
,这可以在publicKeyCredential.response.authenticatorData
属性中找到。
身份验证器数据采用具有一致结构的
ArrayBuffer
形式——参见身份验证器数据。身份验证器扩展结果数据始终位于末尾的一个部分,作为一个表示结果的 CBOR 映射。有关完整身份验证器数据结构的详细描述,请参阅AuthenticatorAssertionResponse.authenticatorData
。回到我们的示例,如果依赖方被授权接收
minPinLength
值,身份验证器数据将包含以下形式的表示:"minPinLength": uint
。 - 对于成功
可用扩展
下面的扩展并不代表所有可用扩展的详尽列表。我们选择记录我们已知已标准化并至少被一个渲染引擎支持的扩展。
appid
- 可用范围:身份验证 (
get()
) - 由:用户代理 处理
- 规范:FIDO AppID 扩展 (appid)
允许依赖方请求先前使用旧版 FIDO U2F JavaScript API 注册的凭据的断言,避免重新注册凭据的麻烦。appid
是该 API 中与 WebAuthn 中的 rpId
等效的(尽管请记住,appid
采用 URL 形式,而 rpId
采用域名形式)。
输入
publicKey
的 extensions
属性必须包含一个 appid
属性,其值是旧版 API 中使用的应用程序标识符。例如
({
extensions: {
appid: "https://accounts.example.com",
},
});
您还必须在 publicKey
的 allowCredentials
属性中列出 FIDO U2F 凭据 ID,例如
({
allowCredentials: [
{
id: arrayBuffer, // needs to contain decoded binary form of id
transports: ["nfc", "usb"],
type: "public-key",
},
],
});
输出
如果 appid
成功用于断言,则输出 appid: true
,否则输出 appid: false
。
appidExclude
- 可用范围:注册 (
create()
) - 由:用户代理 处理
- 规范:FIDO AppID 排除扩展 (appidExclude)
允许依赖方在注册期间排除包含先前使用旧版 FIDO U2F JavaScript API 注册的指定凭据的身份验证器。这是必需的,因为默认情况下 excludeCredentials
字段的内容被假定为 WebAuthn 凭据。使用此扩展时,您可以在 excludeCredentials
中包含旧版 FIDO U2F 凭据,它们将被识别为旧版凭据。
输入
publicKey
的 extensions
属性必须包含一个 appidExclude
属性,其值是请求按旧版 FIDO U2F 凭据排除身份验证器的依赖方标识符。例如
({
extensions: {
appidExclude: "https://accounts.example.com",
},
});
然后您可以在 publicKey
的 excludeCredentials
属性中列出 FIDO U2F 凭据,例如
({
excludeCredentials: [
{
id: arrayBuffer, // needs to contain decoded binary form of id
transports: ["nfc", "usb"],
type: "public-key",
},
],
});
输出
如果扩展已执行,则输出 appidExclude: true
,否则输出 appidExclude: false
。
credProps
- 可用范围:注册 (
create()
) - 由:用户代理 处理
- 规范:凭据属性扩展 (credProps)
允许依赖方请求有关已创建凭据的附加信息/属性。目前,这仅在调用 create()
并将 publicKey.authenticatorSelection.residentKey
设置为 "preferred"
时有用;它请求有关已创建凭据是否可发现的信息。
输入
publicKey
的 extensions
属性必须包含一个值为 true
的 credProps
属性
({
extensions: {
credProps: true,
},
});
您还必须将 authenticatorSelection.requireResidentKey
设置为 true
,这表示需要常驻密钥。
({
authenticatorSelection: {
requireResidentKey: true,
},
});
输出
如果注册的 PublicKeyCredential
是客户端可发现凭据,则输出以下内容
({
credProps: {
rk: true,
},
});
如果输出中 rk
设置为 false
,则凭据是服务器端凭据。如果输出中没有 rk
,则不知道凭据是客户端可发现的还是服务器端的。
credProtect
- 可用范围:注册 (
create()
) - 由:身份验证器 处理
- 规范:凭据保护 (credProtect)
允许依赖方在创建凭据时指定最低凭据保护策略。
输入
publicKey
的 extensions
属性必须包含一个 credentialProtectionPolicy
属性,用于指定要创建凭据的保护级别,以及一个布尔 enforceCredentialProtectionPolicy
属性,用于指定 create()
调用是否应失败而不是创建不符合指定策略的凭据
({
extensions: {
credentialProtectionPolicy: "userVerificationOptional",
enforceCredentialProtectionPolicy: true,
},
});
可用的 credentialProtectionPolicy
值如下
"userVerificationOptional"
实验性功能-
用户验证是可选的。发送给身份验证器进行处理的等效
credProtect
值为0x01
。 "userVerificationOptionalWithCredentialIDList"
-
仅当凭据可发现(即,它是客户端可发现的)时,用户验证才是可选的。发送给身份验证器进行处理的等效
credProtect
值为0x02
。 "userVerificationRequired"
-
始终需要用户验证。发送给身份验证器进行处理的等效
credProtect
值为0x03
。
注意:Chromium 将默认使用 userVerificationOptionalWithCredentialIDList
或 userVerificationRequired
,具体取决于请求类型
- 当创建凭据时,如果
residentKey
设置为preferred
或required
,Chromium 将请求保护级别为userVerificationOptionalWithCredentialIDList
。(设置requireResidentKey
与required
视为相同。)这确保了简单地拥有安全密钥不会允许查询给定rpId
的可发现凭据的存在。 - 此外,如果
residentKey
为required
且userVerification
为preferred
,则保护级别将提高到userVerificationRequired
。这确保了拥有安全密钥不会允许登录到不需要用户验证的网站。(这不是完全保护;网站仍应仔细考虑其用户的安全性。) - 如果网站请求明确的
credProtect
级别,则该级别将覆盖这些默认值。如果安全密钥的默认级别更高,这些默认值绝不会导致保护级别低于安全密钥的默认级别。
假设 enforceCredentialProtectionPolicy
值为 true
。在这种情况下,如果无法遵守策略(例如,它需要用户验证,但身份验证器不支持用户验证),则 create()
调用将失败。如果为 false
,系统将尽最大努力创建符合策略的凭据,但如果不可能,它仍将尽可能创建符合策略的凭据。
输出
如果 create()
调用成功,身份验证器数据将包含以下形式的 credProtect
值表示,该值表示所设置的策略
({ credProtect: 0x01 });
largeBlob
- 可用范围:注册 (
create()
) 和身份验证 (get()
) - 由:用户代理 处理
- 规范:大 blob 存储扩展 (largeBlob)
允许依赖方在身份验证器上存储与凭据关联的 blob——例如,它可能希望直接存储证书而不是运行集中式身份验证服务。
输入
在 create()
调用期间,publicKey
的 extensions
属性必须包含一个具有以下对象结构的 largeBlob
属性
({
extensions: {
largeBlob: {
support: "required",
},
},
});
support
属性的值是一个字符串,可以是以下之一
"preferred"
:如果可能,凭据将使用可以存储 blob 的身份验证器创建,但如果不能,它仍将创建一个。输出的“supported”属性报告身份验证器存储 blob 的能力。"required"
:凭据将使用身份验证器创建以存储 blob。如果无法做到这一点,create()
调用将失败。
在 get()
调用期间,publicKey
的 extensions
属性必须包含一个具有两个子属性之一——read
或 write
的 largeBlob
属性(如果两者都存在,get()
将失败)
read
属性是一个布尔值。值为 true
表示依赖方希望获取与断言凭据关联的先前写入的 blob
({
extensions: {
largeBlob: {
read: true,
},
},
});
write
属性的值是 ArrayBuffer
、TypedArray
或 DataView
,表示依赖方希望与现有凭据一起存储的 blob
({
extensions: {
largeBlob: {
write: arrayBuffer,
},
},
});
注意:要使写入身份验证操作成功,publicKey.allowCredentials
必须只包含一个表示您希望 blob 存储在其旁边的凭据的元素。
输出
如果注册的凭据能够存储 blob,则成功的 create()
调用会提供以下扩展输出
({
largeBlob: {
supported: true, // false if it cannot store blobs
},
});
如果成功,get()
读取调用会将 blob 作为 ArrayBuffer
在扩展输出中提供
({
largeBlob: {
blob: arrayBuffer,
},
});
注意:如果失败,将返回 largeBlob
对象,但不会出现 blob
。
get()
写入调用通过扩展输出中的 written
布尔值指示写入操作是否成功。true
值表示已成功写入关联的身份验证器,false
表示不成功。
({
largeBlob: {
written: true,
},
});
minPinLength
- 可用范围:注册 (
create()
) - 由:身份验证器 处理
- 规范:最小 PIN 长度扩展 (minPinLength)
允许依赖方请求身份验证器的最小 PIN 长度。
输入
publicKey
的 extensions
属性必须包含一个值为 true
的 minPinLength
属性
({
extensions: {
minPinLength: true,
},
});
输出
如果依赖方被授权接收 minPinLength
值(如果其 rpId
存在于身份验证器的授权依赖方列表中),则身份验证器数据将包含以下形式的表示
({ minPinLength: uint });
如果依赖方未授权,则扩展将被忽略,并且不提供 "minPinLength"
输出值。
payment
允许依赖方请求创建可用于安全支付确认的 WebAuthn 凭据——由依赖方和其他方共同使用;请参阅使用安全支付确认。
输入
payment
扩展的输入在 AuthenticationExtensionsPaymentInputs 字典中定义
isPayment
-
一个布尔值,指示扩展是否处于活动状态。
rpID
-
正在使用的凭据的依赖方 ID。仅在身份验证时使用;不用于注册。
topOrigin
-
顶级框架的来源。仅在身份验证时使用;不用于注册。
payeeName
-
如果存在,显示给用户的收款人名称。仅在身份验证时使用;不用于注册。
payeeOrigin
-
如果存在,显示给用户的收款人来源。仅在身份验证时使用;不用于注册。
total
-
显示给用户的交易金额。仅在身份验证时使用;不用于注册。总额类型为 PaymentCurrencyAmount。
instrument
-
显示给用户的工具详细信息。仅在身份验证时使用;不用于注册。工具类型为 PaymentCredentialInstrument。
输出
None
prf
- 可用范围:注册 (
create()
) 和身份验证 (get()
) - 由:用户代理 处理
- 规范:伪随机函数扩展 (prf)
允许依赖方从与凭据关联的伪随机函数 (PRF) 获取一个或两个输入的输出。PRF 实际上是一个随机预言机——一个对任何给定输入返回随机值,但对相同输入总是返回相同值的函数。
生成与用户凭据关联的随机数的能力在许多加密应用程序中都很有用。例如,它可以用于生成对称密钥以加密敏感数据,并且只能由拥有种子和关联身份验证器的用户解密。它也可以类似地用于创建端到端加密的对称密钥,该密钥使用来自服务器的值作为种子,并且对于该凭据和会话是唯一的。
该扩展允许您将 ArrayBuffer
或 TypedArray
类型的缓冲区值传递给身份验证器,身份验证器将返回使用关联凭据的 PRF 评估该值的结果。这可以在断言中完成,作为身份验证工作流的一部分——指定要评估结果的一个或多个凭据。它也可以在创建凭据时完成;但支持在创建凭据时生成输出的身份验证器较少。
输入
在 create()
调用期间,publicKey
的 extensions
属性可能包含一个 prf
属性,该属性具有 eval
对象,其中包含 first
属性和可选的 second
属性。这些属性是 ArrayBuffer
或 TypedArray
实例,其中包含要传递给凭据的 PRF 的值。
例如,以下定义可以在创建新凭据时使用,以便从服务器提供的秘密创建新的对称密钥。
({
extensions: {
prf: {
eval: { first: new TextEncoder().encode("Salt for new symmetric key") },
},
},
});
如果需要为凭据创建两个随机值,例如在每次会话旋转加密密钥的工作流中,可以使用可选的 second
属性。例如,在此类工作流中,在每个会话中您传递两个盐值:first
盐值返回一个可用于解密上一会话数据的值,而 second
盐值返回一个可用于加密此会话数据的值。在后续会话中,second
盐值移动到 first
盐值的位置,从而限制了特定盐值可能被有效泄露的生命周期。
({
extensions: {
prf: {
eval: {
first: currentSessionKey, // salt for current session
second: nextSessionKey, // salt for next session
},
},
},
});
create()
调用可能因以下异常而拒绝
NotSupportedError
DomException
eval
对象中存在evalByCredential
键。
请注意,在创建凭据时评估 PRF 可能不受支持,并且这将在输出中报告。您仍然可以尝试在断言中评估 PRF,如下所示。
在 get()
调用期间,publicKey
的 extensions
属性可能包含一个带有 evalByCredential
子属性的 prf
属性。这是一个对象,将 Base64 URL 编码的凭据 ID 映射到与上述相同形式的评估对象。换句话说,这允许您指定要评估不同凭据的值。
({
extensions: {
prf: {
evalByCredential: {
"<credentialId>": { first: bufferOne, second: bufferTwo },
// …
"<credentialId2>": {
first: anotherBufferOne,
second: anotherBufferTwo,
},
},
},
},
});
get()
调用可能因以下异常而拒绝
NotSupportedError
DomException
-
如果
eval
是prf
对象,或者当evalByCredential
不为空时allowCredentials
为空。 SyntaxError
DomException
-
evalByCredential
中的任何键都是空字符串或不是有效的 Base64 URL 编码,或者与publicKey.allowCredentials
中的某个元素的 ID 不匹配。
输出
如果注册的凭据支持在创建凭据时使用 PRF,则成功的 create()
调用会提供以下扩展输出。
({
prf: {
enabled: true, // PRF can be used when creating credentials.
results: { first: outputBuffer1, second: outputBuffer2 },
},
});
enabled
属性指示在创建凭据时是否可以使用 PRF。first
和 second
属性包含评估输入上的 first
和 second
的结果,如果未指定相应的输入,则将省略 second
。
如果身份验证器不支持在创建时使用 PRF,则 create()
的输出将如下所示
({
prf: {
enabled: false, // PRF cannot be used when creating credentials.
},
});
get()
返回与 create()
相同的 prf
对象,具有相同的结构,只是它省略了 enabled
键。该对象包含与用户选择的凭据的输入相对应的 PRF 值。
({
prf: {
results: { first: outputBuffer1, second: outputBuffer2 },
},
});
请注意,enabled
仅作为 create()
的输出存在,并指示在创建凭据时身份验证器是否支持 PRF。如果身份验证器根本不支持 PRF,则 get()
调用的结果将是
({
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
加载中…