网络身份验证扩展

The Web Authentication API has a system of extensions — extra functionality that can be requested during credential creation (navigator.credentials.create()) or authentication (navigator.credentials.get()) operations. This article explains how to request WebAuthn extensions, retrieve information about the responses from those requests, and the available extensions — including browser support and expected inputs and outputs.

如何使用 WebAuthn 扩展

When invoking navigator.credentials.create() or navigator.credentials.get(), the publicKey object parameter required to initiate a WebAuthn flow can include an extensions property. The value of extensions is itself an object, the properties of which are the input values for any extensions the relying party wishes to request the use of in the method you call.

Behind the scenes, the inputs are processed by the user agent and/or the authenticator.

For example, in a publicKey object for a create() call, we might want to request the use of two extensions

  1. The credProps extension. Relying parties set credProps to request that the browser tells the relying party whether the credential is resident/discoverable after registration. This is useful when calling create() with publicKey.authenticatorSelection.residentKey = "preferred". To request it, you also need to set publicKey.extensions.credProps = true when the browser makes a credential and, depending on the type of authenticator used, it will be discoverable (for example, the FIDO2 authenticator would typically make it discoverable; FIDO1/U2F security key would be non-discoverable). credProps is processed by the user agent only.
  2. The minPinLength extension allows relying parties to request the authenticator's minimum PIN length. This requires extensions.minPinLength to be set to true. minPinLength is processed by the authenticator, with the user agent only serving to pass the input data along to it.
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
  }
}

We can then pass the publicKey object into a create() call to initiate the credential creation flow

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

检索扩展请求结果

If successful, the create() call will return a Promise that resolves with a PublicKeyCredential object. Once extension processing has completed, the results of the processing are communicated in the response (although not in all cases — it is possible for extensions to have no output).

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);
  });

As demonstrated by the above code snippet, there are two different places to find your output extension results

  1. You can find the results of client (user agent) extension processing by calling the PublicKeyCredential.getClientExtensionResults() method. This returns a map, with each entry being an extensions' identifier string as the key, and the output from the processing of the extension by the client as the value. In the example above, if the browser supported the credProps extension and it was processed correctly, the myClientExtResults map object would contain one entry, "credProps", with a value of { rk: true }. This would verify that the created credential is indeed discoverable.
  2. You can find the results of authenticator extension processing in the authenticator data for the operationAuthenticator data takes the form of an ArrayBuffer with a consistent structure — see authenticator data. The authenticator extension results data is always found in a section at the end, as a CBOR map representing the results. See AuthenticatorAssertionResponse.authenticatorData for a detailed description of the complete authenticator data structure. Back to our example, if the relying party is authorized to receive the minPinLength value, the authenticator data would contain a representation of it in the following form: "minPinLength": uint.

可用扩展

The extensions below do not represent an exhaustive list of all the extensions available. We have elected to document extensions that we know to be standardized and supported by at least one rendering engine.

appid

Allows a relying party to request an assertion for a credential previously registered using the legacy FIDO U2F JavaScript API, avoiding the hassle of re-registering the credential. The appid is that API's equivalent to the rpId in WebAuthn (although bear in mind that appids are in the form of URLs, whereas rpIds are in the form of domains).

Input

The publicKey's extensions property must contain an appid property, the value of which is the application identifier used in the legacy API. For example

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

You must also list the FIDO U2F credential IDs in the publicKey's allowCredentials property, for example

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

Output

Outputs appid: true if the appid was successfully used for the assertion, or appid: false otherwise.

appidExclude

Allows a relying party to exclude authenticators containing specified credentials previously registered using the legacy FIDO U2F JavaScript API during registration. This is required because by default the contents of the excludeCredentials field are assumed to be WebAuthn credentials. When using this extension, you can include legacy FIDO U2F credentials inside excludeCredentials, and they will be recognized as such.

Input

The publicKey's extensions property must contain an appidExclude property, the value of which is the identifier of the relying party requesting to exclude authenticators by legacy FIDO U2F credentials. For example

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

You can then list FIDO U2F credentials in the publicKey's excludeCredentials property, for example

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

Output

Outputs appidExclude: true if the extension was acted upon, or appidExclude: false otherwise.

credProps

Allows a relying party to request additional information/properties about the credential that was created. This is currently only useful when calling create() with publicKey.authenticatorSelection.residentKey = "preferred"; it requests information on whether the created credential is discoverable.

Input

The publicKey's extensions property must contain a credProps property with a value of true

js
extensions: {
  credProps: true;
}

You must also set authenticatorSelection.requireResidentKey to true, which indicates that a resident key is required.

js
authenticatorSelection: {
  requireResidentKey: true;
}

Output

Outputs the following if the registered PublicKeyCredential is a client-side discoverable credential

js
credProps: {
  rk: true;
}

If rk is set to false in the output, the credential is a server-side credential. If rk is not present in the output, then it is not known whether the credential is client-side discoverable or server-side.

credProtect

Allows a relying party to specify a minimum credential protection policy when creating a credential.

Input

The publicKey's extensions property must contain a credentialProtectionPolicy property specifying the protection level of the credential to be created, and a boolean enforceCredentialProtectionPolicy property specifying whether the create() call should fail rather than creating a credential that does not conform to the specified policy

js
extensions: {
  credentialProtectionPolicy: "userVerificationOptional",
  enforceCredentialProtectionPolicy: true
}

The available credentialProtectionPolicy values are as follows

"userVerificationOptional" Experimental

User verification is optional. The equivalent credProtect value sent to the authenticator for processing is 0x01.

"userVerificationOptionalWithCredentialIDList"

User verification is optional only if the credential is discoverable (i.e., it is client-side discoverable). The equivalent credProtect value sent to the authenticator for processing is 0x02.

"userVerificationRequired"

用户验证始终是必需的。发送到身份验证器以进行处理的等效credProtect值为0x03

注意:Chromium 将默认使用userVerificationOptionalWithCredentialIDListuserVerificationRequired,具体取决于请求类型。

  • 如果将residentKey设置为preferredrequired,Chromium 在创建凭据时将请求userVerificationOptionalWithCredentialIDList的保护级别。(将requireResidentKey设置为required与将residentKey设置为required相同。)这确保了简单的安全密钥的物理占有不会允许查询给定rpId的可发现凭据的存在。
  • 此外,如果residentKeyrequired并且userVerificationpreferred,则保护级别将提升至userVerificationRequired。这确保了安全密钥的物理占有不会允许登录到不需要用户验证的网站。(这不是完全保护;网站仍应仔细考虑其用户的安全。)
  • 如果网站请求显式的credProtect级别,则将覆盖这些默认值。这些默认值永远不会导致保护级别低于安全密钥的默认值(如果安全密钥的默认值更高)。

假设enforceCredentialProtectionPolicy值为true。在这种情况下,如果无法遵守策略(例如,它需要用户验证,但身份验证器不支持用户验证),则create()调用将失败。如果它为false,系统将尽力创建符合策略的凭据,但如果无法实现,它仍然会创建一个尽可能符合策略的凭据。

Output

如果create()调用成功,则身份验证器数据将包含一个表示credProtect值的表示形式,该值以以下形式表示设置的策略。

js
{ "credProtect": 0x01 }

largeBlob

允许依赖方在身份验证器上存储与凭据关联的 Blob——例如,它可能希望直接存储证书而不是运行集中的身份验证服务。

Input

create()调用期间,publicKeyextensions属性必须包含一个largeBlob属性,该属性具有以下对象结构

js
extensions: {
  largeBlob: {
    support: "required";
  }
}

support属性的值是一个字符串,可以是以下之一

  • "preferred":如果可能,凭据将使用能够存储 Blob 的身份验证器创建,但如果不可能,它仍然会创建凭据。输出的support属性报告了身份验证器存储 Blob 的能力。
  • "required":凭据将使用能够存储 Blob 的身份验证器创建。如果不可能,create()调用将失败。

get()调用期间,publicKeyextensions属性必须包含一个largeBlob属性,该属性具有两个子属性之一——readwrite(如果两者都存在,则get()失败)。

read属性是一个布尔值。值为true表示依赖方希望获取与断言的凭据关联的先前写入的 Blob。

js
extensions: {
  largeBlob: {
    read: true;
  }
}

write属性的值为一个ArrayBufferTypedArrayDataView,它表示依赖方希望与现有凭据一起存储的 Blob。

js
extensions: {
  largeBlob: {
    write: arrayBuffer;
  }
}

注意:要使写入身份验证操作成功,publicKey.allowCredentials必须只包含一个元素,表示您希望 Blob 与其一起存储的凭据。

Output

如果注册的凭据能够存储 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 长度。

Input

publicKeyextensions属性必须包含一个minPinLength属性,其值为true

js
extensions: {
  minPinLength: true;
}

Output

如果依赖方有权接收minPinLength值(如果其rpId存在于身份验证器的授权依赖方列表中),则身份验证器数据将包含以下形式的表示形式

js
{"minPinLength": uint}

如果依赖方未经授权,则会忽略该扩展,并且不会提供"minPinLength"输出值。

payment

允许依赖方请求创建 WebAuthn 凭据,该凭据可由依赖方和其他方使用——使用安全支付确认;请参阅使用安全支付确认

Input

payment扩展的输入在AuthenticationExtensionsPaymentInputs 字典中定义

isPayment

一个布尔值,表示该扩展处于活动状态。

rpID

正在使用的凭据的依赖方 ID。仅在身份验证时使用;不在注册时使用。

topOrigin

顶级框架的来源。仅在身份验证时使用;不在注册时使用。

payeeName

收款人姓名(如果存在),显示给用户。仅在身份验证时使用;不在注册时使用。

payeeOrigin

收款人来源(如果存在),显示给用户。仅在身份验证时使用;不在注册时使用。

total

显示给用户的交易金额。仅在身份验证时使用;不在注册时使用。总金额的类型为PaymentCurrencyAmount

instrument

显示给用户的工具详细信息。仅在身份验证时使用;不在注册时使用。工具的类型为PaymentCredentialInstrument

Output

规范

WebAuthn 扩展的规范位置有很多。IANA 的WebAuthn 扩展标识符提供所有扩展的注册表,但请注意,一些扩展可能已弃用。

指定扩展的位置

浏览器兼容性

WebAuthn 扩展的兼容性数据已细分为两个表格——可在凭据注册期间使用的扩展(create())以及可在身份验证期间使用的扩展(get())。一些扩展可以在两种操作期间使用。

api.CredentialsContainer.create.publicKey_option.extensions

BCD 表格仅在浏览器中加载

api.CredentialsContainer.get.publicKey_option.extensions

BCD 表格仅在浏览器中加载