Skip to content

结论速答

  • 浏览器不会把“品牌/提供方”直接告诉前端(这是刻意的隐私设计)。你无法仅凭 navigator.credentials.get() 的返回结果直接断言“用户点的是 Google、Windows Hello、Android 手机还是 Apple 钥匙串”。(W3C)
  • 能拿到的是认证器类别credential.authenticatorAttachment"platform"(平台,如 Windows Hello/Touch ID/Android 平台密钥)或 "cross-platform"(漫游,如安全钥/YubiKey,或手机经 Hybrid/蓝牙/扫码)。但它不是品牌级别。(MDN Web Docs)
  • 如需在 UI 中展示“来自 Google / Apple / Windows Hello”等,正确做法是:在注册时记录 AAGUID → 服务端映射到提供方;认证时根据 rawId 找回该凭据的“提供方标签”并返回给前端显示。注意:移动端大多用 none attestation,AAGUID 仅用于展示,不应作为安全决策。(web.dev)

能拿到的前端信号

1) 认证器“类别”

  • credential.authenticatorAttachment"platform" / "cross-platform"。仅代表本机平台还是漫游/跨设备不能细化到“Google/Apple/Windows”。(MDN Web Docs)

例:若你想在“跨设备登录”后建议用户把凭据保存到本机,可用它来判断(Chrome 指南里就是这么建议的)。(web.dev)

2) 扩展结果(可选)

  • credential.getClientExtensionResults():如你在 publicKey.extensions 里请求了扩展,这里会返回结果对象。它可能用于风险/设备判别(如 devicePubKey 帮你识别具体使用的那台设备),但仍不揭示“品牌”。(MDN Web Docs)

3) 客户端能力/提示(非识别)

  • PublicKeyCredential.getClientCapabilities() 可让你探知是否支持 hybrid transport / 条件式自动填充等,以便调整 UI,但不是“用的是谁家”。(MDN Web Docs)

想显示“Google/Apple/Windows”的正确方案(服务端主导)

A. 注册时:记录 AAGUID → 映射“提供方”

  • AAGUID 是“认证器型号/提供方”的 UUID。注册时create())在服务端解析并保存到凭据表,后续用于 UI 文案(例如“由 Google 密码管理器创建”)。官方示例与社区清单可直接映射 AAGUID → 提供方名称/图标。注意:无签名背书的场景仅用于展示。(web.dev)

复杂逻辑:提取 AAGUID 并存库

ts
// 上一行:SimpleWebAuthn 注册校验后,拿到 AAGUID 并持久化(仅展示用途)
const { verified, registrationInfo } = await verifyRegistrationResponse(...);
const aaguid = registrationInfo?.aaguid; // 可能为 '00000000-0000-0000-0000-000000000000'
// 上一行:用官方推荐的社区清单将 AAGUID → 提供方标签(Google/Apple/…)
const provider = mapAAGUIDToProvider(aaguid); // 例:'Google Password Manager' | 'iCloud Keychain' | ...
await db.credentials.upsert({ credentialId, userId, aaguid, providerLabel: provider });

资料:如何用 AAGUID 判定提供方 & 社区清单链接(web.dev 文章)。(web.dev)

B. 认证时:凭 rawId 找回“提供方标签”

  • 登录 get() 成功后,前端把 rawId 发回;服务端校验通过后,根据 rawId 查询凭据表,把保存的 providerLabel 回传给前端用于 UI 展示。
  • SimpleWebAuthn 还会告诉你凭据类型/是否多设备credentialDeviceType / credentialBackedUp),可做补充标签(例如“多设备同步的 passkey”)。(简单网页认证)

仅做“类别”与“跨设备”判断时(无需品牌)

只改你现有前端必要行(按你的要求“只给改动”):

ts
// 上一行:读取认证器类别(platform/cross-platform),随 payload 发回,便于统计/风控/UI
const attachment = (cred as PublicKeyCredential).authenticatorAttachment as
  | "platform"
  | "cross-platform"
  | null;

// 上一行:如你启用了扩展,这里拿到客户端扩展结果(可能含 devicePubKey 等),可一并上传分析
const clientExt = (cred as PublicKeyCredential).getClientExtensionResults?.();

// 上一行:把两个“提示性”字段附在 payload 里(非安全必需,仅用于展示/分析)
payload.meta = {
  authenticatorAttachment: attachment,
  clientExtensionResults: clientExt,
};

为什么不能直接知道“Google / Apple / Windows Hello”

  • 规范与浏览器刻意最小化披露:避免把用户的设备/账户生态暴露给站点(隐私原则)。API 仅给“类别”(platform/cross-platform),不会告诉你“是 Google 还是 Apple”。(W3C)
  • AAGUID 仅在注册态可得且常为匿名化:很多平台默认 none attestation,即便有 AAGUID,也只适合做 UI 标签,不应做强安全策略。(web.dev)

进阶:区分“手机(Hybrid)” vs “U 盘钥匙”

  • 严格来说,结果里也不直接告诉你"cross-platform" 既可能是 安全钥(USB/NFC/BLE),也可能是手机经 Hybrid。(MDN Web Docs)

  • 可行的推断思路(仍非 100% 准确):

    1. 检查客户端能力是否支持 hybridTransport(仅做 UI/流程分支)。(MDN Web Docs)
    2. 在请求里开启 devicePubKey 扩展并在服务端校验其输出,可用于识别“这次是同一台手机/设备”(设备指纹化的合规替代),但仍不是品牌信息。(GitHub)

小抄:你可以立刻加的最小改动

前端(认证)——只贴需新增行:

ts
// 上一行:把 authenticatorAttachment 带回去,便于服务端记录这次是平台还是漫游
const attachment =
  (cred as PublicKeyCredential).authenticatorAttachment ?? null;

// 上一行:如你要研究扩展结果(例如 devicePubKey),连同其他字段一起上传
const ext = (cred as PublicKeyCredential).getClientExtensionResults?.() ?? {};
await loginByCredential({ ...payload, meta: { attachment, ext } });

服务端(注册验证后)——只贴需新增行:

ts
// 上一行:注册成功后,提取 AAGUID 并映射“提供方标签”(仅显示用途)
const { aaguid } = verification.registrationInfo!;
const providerLabel = mapAAGUIDToProvider(aaguid); // 例:'Google Password Manager' | 'iCloud Keychain' | 'Windows Hello' | 'YubiKey' | 'Unknown'
await saveCredential({ credentialId, userId, aaguid, providerLabel });

服务端(认证成功后)——只贴需新增行:

ts
// 上一行:用 rawId 找回凭据,连同 providerLabel 返回前端做 UI 展示
const cred = await getCredentialById(credentialId);
return { ok: true, providerLabel: cred?.providerLabel ?? "Unknown" };

参考

  • authenticatorAttachment 仅提供类别(platform / cross-platform),不包含品牌。(MDN Web Docs)
  • 用 AAGUID 在注册时标注“哪个提供方创建了该 passkey”(仅用于展示;无背书时不要做安全决策)。(web.dev)
  • WebAuthn 的隐私/最小披露模型(无法直接知道品牌)。(W3C)
  • 客户端能力探测(hybridTransport/conditional UI 等)。(MDN Web Docs)
  • SimpleWebAuthn:注册/认证与 multiDevice/credentialBackedUp 语义,用于补充标签。(简单网页认证)

需要的话,我可以顺手给你一份AAGUID→ 提供方的 JSON 映射样例(含 Google/Apple/YubiKey/Windows Hello 的常见 AAGUID),以及注册环节如何在 none attestation 下做“仅展示用途”的安全说明

好嘞,给你一套可直接用的 AAGUID → 提供方映射样例(JSON),再配后端最小改动片段(注册提取并存 AAGUID、登录返回 providerLabel)。我只给必要片段,复杂逻辑在上一行写中文注释,符合你的习惯。


1) AAGUID → 提供方映射(建议放到 aaguid.providers.ts

说明:这只是用于 UI 展示的“友好标签”,不要据此做安全决策;Apple/Safari 常见全 0 AAGUID,无法唯一指向 Apple。Google/Windows/YubiKey 取值参考了权威与社区清单(见文末引用)。

ts
// 上一行:AAGUID → 人类可读标签(仅展示用途)
export const AAGUID_PROVIDER_MAP: Record<string, string> = {
  // Google Password Manager(Chrome/Android 等)
  "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": "Google Password Manager", // :contentReference[oaicite:0]{index=0}

  // Windows Hello(多种形态)
  "6028b017-b1d4-4c02-b4b3-afcdafc96bb2": "Windows Hello (Software)", // :contentReference[oaicite:1]{index=1}
  "9ddd1817-af5a-4672-a2b9-3e3dd95000a9": "Windows Hello (VBS Hardware)", // :contentReference[oaicite:2]{index=2}
  "08987058-cadc-4b81-b6e1-30de50dcbe96": "Windows Hello (Hardware)", // :contentReference[oaicite:3]{index=3}

  // Apple iCloud Keychain:常见 none attestation → 全 0(仅提示用,不可靠)
  "00000000-0000-0000-0000-000000000000":
    "Unknown (可能是 iCloud Keychain / none attestation)", // :contentReference[oaicite:4]{index=4}

  // YubiKey(示例,型号/固件不同对应不同 AAGUID;更多见官方表)
  "ee882879-721c-4913-9775-3dfcce97072a": "YubiKey 5 (USB-A/5.2/5.4)", // :contentReference[oaicite:5]{index=5}
  "2fc0579f-8113-47ea-b116-bb5a8db9202a": "YubiKey 5 NFC / 5C NFC (5.2/5.4)", // :contentReference[oaicite:6]{index=6}
  "fa2b99dc-9e39-4257-8f92-4a30d23c4118": "YubiKey 5 NFC (5.1)", // :contentReference[oaicite:7]{index=7}
  "f8a011f3-8c0a-4d15-8006-17111f9edc7": "Security Key by Yubico (Blue/5.1)", // :contentReference[oaicite:8]{index=8}
};

可选增强:你也可以在启动时拉取社区维护清单passkeydeveloper/passkey-authenticator-aaguids)缓存合并,用于展示;该清单不保证长期可用且不是安全来源。(GitHub)


2) 映射函数(空/未知兜底)

ts
// 上一行:把 AAGUID 映射成“展示用标签”,未知或空值兜底
export function mapAAGUIDToProvider(aaguid?: string | null): string {
  if (!aaguid) return "Unknown";
  const key = aaguid.toLowerCase();
  return AAGUID_PROVIDER_MAP[key] ?? "Unknown";
}

3) 注册成功后,提取并持久化 AAGUID(NestJS + simplewebauthn v11+)

ts
// 上一行:注册校验成功后,提取 aaguid,仅用于 UI 展示(不要做安全准入)
const { verified, registrationInfo } = await verifyRegistrationResponse(params);
if (!verified || !registrationInfo)
  throw new UnauthorizedException("Invalid attestation");

const aaguid = registrationInfo.aaguid ?? null;
const providerLabel = mapAAGUIDToProvider(aaguid);

// 上一行:把 aaguid / providerLabel 和 credentialId、公钥、counter 等一起存库
await credentialsRepo.upsert({
  userId,
  credentialId: base64url(credentialIDBuffer),
  publicKey: base64url(publicKeyBuffer),
  counter: registrationInfo.credential?.counter ?? 0,
  aaguid,
  providerLabel, // ← 认证时可直接回传做 UI 展示
});

注:很多平台/浏览器默认 none attestation,AAGUID 可能是全 0 或不可用;因此只能展示不要据此放行/拒绝。(Stack Overflow)


4) 认证成功后,把 providerLabel 回给前端(方便 UI 显示“来自…”)

ts
// 上一行:校验断言通过后,用 rawId 定位凭据
const cred = await credentialsRepo.findById(payload.rawId);

// 上一行:把之前存的 providerLabel 返回给前端,仅作展示
return {
  ok: true,
  providerLabel: cred?.providerLabel ?? "Unknown",
  // 也可以带上 credentialDeviceType/credentialBackedUp 做补充标签
};

5) 关键注意

  • Apple / Safari:经常拿到全 0 AAGUID(或拿不到),这是隐私设计 + none attestation 的结果,不能唯一指向“Apple”。(Apple Developer)
  • Windows Hello:存在多个 AAGUID(Software / Hardware / VBS Hardware),上面的三枚是公开常见值。(corbado.com)
  • Google Password Manager:目前常见 AAGUID 为 ea9b8d66-...(历史上有过变化)。(谷歌集团)
  • YubiKey型号 + 固件版本不同会有不同 AAGUID,请以官方表为准(我在样例里只挑了几条常用)。(Yubico Support)
  • 用途边界:AAGUID 适合给用户看(“这把凭据来自 … ”),安全策略应以 FIDO MDS、证书链、政策(UV/UP、signCount 等)为主。(W3C)

参考

  • Google Password Manager AAGUID(官方/社区多处一致)。(corbado.com)
  • Windows Hello 常见 AAGUID。(corbado.com)
  • YubiKey 官方 AAGUID 列表(持续更新)。(Yubico Support)
  • Apple/iCloud Keychain 常见全 0 AAGUID(none attestation)。(Apple Developer)
  • web.dev:用 AAGUID 仅做展示用途的最佳实践与社区清单。(web.dev)

需要的话我可以再给一版带图标的映射(顺手接入 webauthn.passwordless.id 的图标 CDN,命名为 {aaguid}-light.png / {aaguid}-dark.png),这样前端账号设置页能直接显示小图标。(webauthn.passwordless.id)

本站总访问