管理服务器和客户端之间的转换并确保一切都是正确的类型有点痛苦,但下面的示例对我有用。我使用了https://github.com/abergs/fido2-net-lib提供的示例,发现它非常有用。
下面是我的第二个因素凭据创建选项服务器端点返回的示例 JSON 数据结构(WebAuthn 指定为 ArrayBuffers 的属性的 base64url 编码值,我还缩短了 pubKeyCredParams 数组):
{
"rp": {
"id": "localhost",
"name": "IDS4"
},
"user": {
"name": "joe.bloggs@acme.com",
"id": "YTNmZTAxYWUtODlhYS00NDEzLTgxYzQtZWJmZjk0MmI5MTVj",
"displayName": "Your ACME account"
},
"challenge": "P8_m1vd5tcMDD9e0SeST4w",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"timeout": 60000,
"attestation": "indirect",
"authenticatorSelection": {
"authenticatorAttachment": "cross-platform",
"requireResidentKey": false,
"userVerification": "discouraged"
},
"excludeCredentials": [
{
"type": "public-key",
"id": "A_IySAe38xFIoTUbAFyAUIrgawhcPOD_xbBDf_UqkvJc_GR37-jRXccYE04A5CmhA3kG8WTGPZP63MiQQ2ykDQ"
}
],
"extensions": {
"exts": true,
"uvi": true,
"loc": true,
"uvm": true,
"biometricPerfBounds": {
"FAR": 3.4028235E+38,
"FRR": 3.4028235E+38
}
}
}
需要强制转换为 ArrayBuffer 的值是:
challenge
user
.id
excludeCredentials
[ n
]。id
辅助函数 - WebAuthnHelpers.js:
class WebAuthnHelpers {
static coerceToArrayBuffer(input) {
if (typeof input === "string") {
// base64url to base64
input = input.replace(/-/g, "+").replace(/_/g, "/");
// base64 to Uint8Array
var str = window.atob(input);
var bytes = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
input = bytes;
}
// Array to Uint8Array
if (Array.isArray(input)) {
input = new Uint8Array(input);
}
// Uint8Array to ArrayBuffer
if (input instanceof Uint8Array) {
input = input.buffer;
}
// error if none of the above worked
if (!(input instanceof ArrayBuffer)) {
throw new TypeError("could not coerce '" + name + "' to ArrayBuffer");
}
return input;
}
static coerceToBase64Url(input) {
// Array or ArrayBuffer to Uint8Array
if (Array.isArray(input)) {
input = Uint8Array.from(input);
}
if (input instanceof ArrayBuffer) {
input = new Uint8Array(input);
}
// Uint8Array to base64
if (input instanceof Uint8Array) {
var str = "";
var len = input.byteLength;
for (var i = 0; i < len; i++) {
str += String.fromCharCode(input[i]);
}
input = window.btoa(str);
}
if (typeof input !== "string") {
throw new Error("could not coerce to string");
}
// base64 to base64url
// NOTE: "=" at the end of challenge is optional, strip it off here
input = input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");
return input;
}
}
例如
credentialCreateOptions.challenge = WebAuthnHelpers.coerceToArrayBuffer(credentialCreateOptions.challenge);
credentialCreateOptions.user.id = WebAuthnHelpers.coerceToArrayBuffer(credentialCreateOptions.user.id);
credentialCreateOptions.excludeCredentials = credentialCreateOptions.excludeCredentials.map((c) =>
{
c.id = WebAuthnHelpers.coerceToArrayBuffer(c.id);
return c;
});
if (credentialCreateOptions.authenticatorSelection.authenticatorAttachment === null) credentialCreateOptions.authenticatorSelection.authenticatorAttachment = undefined;
完成后,我可以credentialCreateOptions
直接传递给navigator.credentials.create
({ publicKey
: ... })