在 Web 上验证身份证件
Verify identity documents on the web
2025年6月9日
一句话判断
上传身份证照片做在线身份验证的时代终于要结束了——W3C Digital Credentials API 让网站可以直接从 Wallet 里的证件获取加密验证过的身份信息,用户只需 Face ID 授权。
这场 Session 讲了什么
这个 Session 覆盖了两个完整的 API 实现:网站端的 W3C Digital Credentials API 和 iOS 端的 IdentityDocumentServices framework。
网站端的流程是:服务器构建 ISO 18013-7 格式的 mdoc 请求(包含需要验证的字段、加密信息和签名),前端通过 navigator.credentials.get() 触发系统 UI,用户选择证件来源(Wallet 或第三方 app),授权后加密响应返回给服务器解密验证。整个链路是端到端加密的,浏览器和操作系统都无法读取身份数据。
iOS 端的 Document Provider API 让任何管理身份证件的 app 都能作为验证来源出现在系统选择 UI 中。app 需要注册证件信息、实现一个 UI App Extension 来显示授权界面,并在用户授权后构建加密响应返回给请求方。关键的安全设计是”partial request”——系统先在沙箱中解析请求的一部分用于构建 UI,完整请求只在用户授权后才释放给 app。
值得深挖的点
四层安全防护的设计思路
整个验证流程有四层独立的安全机制,每一层解决一个不同的信任问题。第一层是 request authentication:请求方用证书对请求签名,证件 app 可以验证”谁在请求”。第二层是 response encryption:响应用 HPKE(RFC-9180)端到端加密,即使浏览器和 OS 也无法读取。第三层是 issuer authentication:响应包含 Mobile Security Object(MSO),由证件签发方签名,服务器通过验证证书链来确认”数据是不是真的”。第四层是 mdoc authentication:MSO 中包含设备公钥,验证响应确实来自签发设备,防止证件被复制到多台设备。
这种分层设计的精妙之处在于:每层独立运作,即使某一层被攻破,其他层仍然提供保护。比如 request authentication 防钓鱼——即使恶意网站诱导用户打开页面,它也需要有效的签名证书才能触发正常的验证流程。而 partial request 机制则是操作系统级别的安全隔离——在用户授权前,app 拿到的只是用于构建 UI 的基本信息,完整的可解析请求数据被锁在系统沙箱里。
跨平台验证的工作机制
Session 中演示了一个 Mac 上的验证流程:点击”验证身份”按钮后,Safari 通过”在 iPhone 上继续”的通知引导用户到手机完成操作。底层用的是 FIDO CTAP 协议——这和 passkey 的跨设备流程是同一套技术。这意味着任何实现了 ISO 18013-7 + CTAP 的浏览器或 OS 都能互操作,不限于 Apple 生态。
对网站开发者来说,navigator.credentials.get() 的调用方式是标准化的。你写的代码在支持这些标准的任何浏览器上都能工作。这对需要在多个平台上做身份验证的业务来说是个巨大优势——不需要为每个平台写不同的集成代码。
代码片段
构建 ISO 18013-7 mdoc 请求的服务器端核心逻辑:
// 构建设备请求:声明需要验证的文档类型和字段
const deviceRequest = {
docRequests: [{
docType: "org.iso.18013.5.1.mDL",
nameSpaces: {
"org.iso.18013.5.1": {
"given_name": true,
"family_name": true,
"age_over_21": true,
"portrait": true,
"driving_privileges": true
}
}
}],
readerAuthentication: [] // 稍后填入签名
};
// 生成加密密钥对和 nonce
const nonce = crypto.randomBytes(32);
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
前端调用 Digital Credentials API 触发验证:
const credential = await navigator.credentials.get({
digital: {
requests: [{
protocol: "org-iso-mdoc",
data: serverRequest // 从服务器获取的已签名请求
}]
}
});
// 将加密响应发回服务器解密验证
await fetch('/verify', { method: 'POST', body: JSON.stringify(credential) });
iOS Document Provider 注册证件:
let store = IdentityDocumentProviderRegistrationStore()
let registration = MobileDocumentRegistration(
mobileDocumentType: .mDL,
trustedAuthorityKeyIdentifiers: [authorityKeyId],
documentIdentifier: docId
)
try await store.addRegistration(registration)
最佳实践
建议网站开发者先在 development-signed build 上测试完整流程,因为开发环境不需要 Oblivious HTTP Relay 审批。在提交到 App Store 之前,必须完成 Apple 的 capability 申请流程。
优先使用 navigator.credentials.get() 的标准错误处理——API 提供了丰富的异常类型,不要用 try-catch 包裹后静默失败。考虑降级方案:如果 API 不可用(比如旧浏览器),回退到传统的表单上传方式。
避免在请求中只验证最少字段。根据业务需要合理选择验证字段,但也要注意隐私——只请求你真正需要的信息,用户在授权界面会看到完整的请求列表。
还有什么值得关注
- 验证请求的签名需要从 Apple Business Connect 获取证书,如果是验证非 Wallet 的证件(如政府 app),需要联系对应签发方获取签名要求。
- IdentityDocumentServices framework 的
performRegistrationUpdates()方法会在用户授权验证时被系统调用,用来同步 app 内的证件注册状态。 - Apple 正在将 Website environments 和 Spatial Image Controls API 等功能作为 developer preview 提供给开发者。