Verify identity documents on the web
App Services 进阶 6m

在 Web 上验证身份证件

Verify identity documents on the web

2025年6月9日

在 Apple 官方观看视频

一句话判断

上传身份证照片做在线身份验证的时代终于要结束了——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 提供给开发者。
应用服务 Safari与Web