跳到主要内容

移动端实现指南

本指南介绍将CDK小部件集成到移动应用程序中的最佳实践。在Web上,小部件通常嵌入在iframe中,但移动应用需要不同的方法来有效显示小部件URL。

概述

CDK小部件是从API调用返回的URL,包含完整的合规流程,包括年龄门控、VPC、数据通知和权限管理。将这些小部件集成到移动应用程序时,您有多种显示选项,每种选项都有不同的功能和权衡。主要考虑因素包括:

  • AgeKeys支持:用户是否可以为未来的验证创建和使用AgeKeys(基于FIDO的通行密钥)
  • 结果通信:小部件结果如何传递回您的应用
  • 用户体验:集成级别和原生感

移动端实现方法

Android选项

Android提供了三种显示小部件URL的主要方法:

  • Chrome Custom Tabs推荐 - 在保持应用品牌的自定义Chrome浏览器标签页中打开小部件URL。这提供了完整的浏览器功能,同时将用户保持在应用的上下文中。Custom Tabs与Chrome共享Cookie和身份验证状态,实现无缝体验。

  • WebView - 使用Android的原生WebView组件在应用内直接嵌入Web内容。虽然实现简单,但WebView对现代Web标准的支持有限,无法访问某些浏览器功能。

不推荐

WebView不支持AgeKeys所需的WebAuthn。使用WebView嵌入小部件时,用户将无法创建或使用AgeKeys。要获得具有完整AgeKeys支持的最佳用户体验,请改用Chrome Custom Tabs。

  • Trusted Web Activity (TWA) - 以全屏模式显示Web内容,主要为Progressive Web Apps设计。TWA需要在应用和k-ID域之间建立数字资产链接。当未检测到数字资产链接时,TWA会自动回退到Chrome Custom Tabs。
不推荐

Trusted Web Activities不支持小部件集成。需要在k-ID的域上配置数字资产链接,但这是不可用的。由于在这种情况下TWA会回退到Chrome Custom Tabs,建议直接使用Chrome Custom Tabs以获得更简单的实现。

iOS选项

iOS提供了三种显示小部件URL的主要方法:

  • ASWebAuthenticationSession推荐 - 专为安全身份验证流程设计,此方法在系统管理的浏览器视图中显示Web内容。它与Safari共享Cookie并提供对现代Web功能(如WebAuthn)的访问,使其成为验证流程的理想选择。

  • SFSafariViewController - 在类似Safari的界面中显示Web内容,与Safari共享Cookie和身份验证状态。这提供了熟悉的浏览体验,同时保持应用上下文。

  • WKWebView - Apple的现代Web视图组件,可在应用内嵌入Web内容。与Android的WebView类似,WKWebView对某些Web标准有限制,无法访问所有浏览器功能。

不推荐

WKWebView不支持AgeKeys所需的WebAuthn。使用WKWebView嵌入小部件时,用户将无法创建或使用AgeKeys。要获得具有完整AgeKeys支持的最佳用户体验,请改用ASWebAuthenticationSession。

AgeKeys支持限制

AgeKeys是基于FIDO和WebAuthn标准的可重用匿名年龄证明凭据。它们允许用户验证一次年龄,并在不同服务之间重复使用该验证,而无需透露个人信息。

AgeKeys限制

AgeKeys需要WebAuthn支持,这在Android WebViewiOS WKWebView中不可用。如果您使用这些组件嵌入小部件,用户在验证期间将看不到AgeKeys作为选项,并且在验证成功后无法创建AgeKeys。

要为您的用户启用AgeKeys,必须使用以下方法之一:

接收小部件结果

移动应用需要在用户完成小部件流程后接收结果。有两种方法,每种方法都有不同的可用性:

回调URL(通用方法)

推荐方法

所有实现方法的推荐方法是使用回调URL。当您调用API生成小部件URL时,包含redirectUrl参数。小部件流程完成后,它会将结果作为查询参数重定向到此URL。

回调URL的优势:

  • 适用于所有实现方法
  • 比DOM消息更可靠
  • 移动应用的标准深度链接模式
  • 即使应用移至后台,结果也会始终传递

回调URL的工作原理

  1. 在应用中注册深度链接处理程序(例如,myapp://vpc-complete
  2. 调用API时将深度链接作为redirectUrl包含
  3. 小部件完成后重定向到您的深度链接
  4. 应用处理深度链接并提取结果
备注

仅当小部件URL在浏览器或Web视图中直接打开时才会发生重定向,而不是在iframe中嵌入时。

回调URL参数

当小部件重定向到您的回调URL时,它包含与小部件类型相关的查询参数。例如:

  • 年龄门控小部件可以包含verificationIdresult
  • 会话相关小部件可以包含sessionId和状态信息

回调URL示例:

myapp://vpc-complete?sessionId=608616da-4fd2-4742-82bf-ec1d4ffd8187&result=PASS

实现回调URL

调用CDK小部件API时,在请求中包含redirectUrl。URL可以是:

  • HTTPS URL:https://example.com/vpc-complete
  • 自定义深度链接:myapp://vpc-complete
备注

并非所有CDK小部件端点都支持redirectUrl。请查看特定API端点文档以了解可用性。当redirectUrl不可用时,请使用WebView或WKWebView的DOM消息。

DOM消息(仅限WebView/WKWebView)

使用Android WebViewiOS WKWebView时,可以监听从小部件发送的JavaScript消息。这允许您:

  • 实时接收小部件结果
  • 控制Web视图何时关闭
  • 根据小部件事件更新应用的UI

CDK小部件根据小部件类型发出不同的DOM事件:

DOM消息作为postMessage事件发送,您可以在本机代码中拦截。有关可用事件的详细信息,请参阅DOM事件概述

有限可用性

DOM消息仅适用于WebView和WKWebView。它们不适用于Chrome Custom TabsTrusted Web ActivityASWebAuthenticationSessionSFSafariViewController

方法比较

方法平台AgeKeysDOM消息回调URL最适合
WebViewAndroid不推荐(无AgeKeys支持)
Chrome Custom TabsAndroid大多数用例(推荐)
Trusted Web ActivityAndroid不推荐(需要数字资产链接)
WKWebViewiOS不推荐(无AgeKeys支持)
ASWebAuthenticationSessioniOS大多数用例(推荐)
SFSafariViewControlleriOS类似Safari的体验

推荐实现

Android: Chrome Custom Tabs

使用Chrome Custom Tabs和回调URL,以获得功能与用户体验的最佳平衡。

选择Chrome Custom Tabs的原因:

  • 通过WebAuthn完全支持AgeKeys
  • 访问所有现代Web功能
  • 应用品牌化的无缝用户体验
  • 可靠的回调机制
  • 与Chrome共享身份验证状态

实现步骤:

  1. 为回调URL注册深度链接处理程序
  2. 在API请求中包含redirectUrl(如果端点支持)
  3. 使用Chrome Custom Tabs打开小部件URL
  4. 使用小部件结果处理深度链接回调

iOS: ASWebAuthenticationSession

使用ASWebAuthenticationSession和回调URL,实现安全、原生感的合规流程。

选择ASWebAuthenticationSession的原因:

  • 通过WebAuthn完全支持AgeKeys
  • 访问所有现代Web功能
  • 系统管理的安全UI
  • 与Safari共享Cookie
  • 可靠的回调机制

实现步骤:

  1. 为回调URL注册URL方案处理程序
  2. 在API请求中包含redirectUrl(如果端点支持)
  3. 使用ASWebAuthenticationSession显示小部件URL
  4. 使用小部件结果处理URL方案回调

完整实现示例

以下是实现推荐方法的逐步示例,包含完整的代码示例:

步骤1:注册深度链接处理程序

Info.plist中注册URL方案:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

步骤2:生成带回调的小部件URL

import Foundation

func generateWidgetUrl(completion: @escaping (URL?) -> Void) {
guard let url = URL(string: "https://game-api.k-id.com/api/v1/widget/generate-e2e-url") else {
completion(nil)
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer YOUR_API_KEY", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let requestBody: [String: Any] = [
"jurisdiction": "US-CA",
"options": [
"redirectUrl": "myapp://vpc-complete"
]
]

guard let httpBody = try? JSONSerialization.data(withJSONObject: requestBody) else {
completion(nil)
return
}
request.httpBody = httpBody

URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(nil)
return
}

guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode),
let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let widgetUrlString = json["url"] as? String,
let widgetUrl = URL(string: widgetUrlString) else {
completion(nil)
return
}
completion(widgetUrl)
}.resume()
}

步骤3:显示小部件

import AuthenticationServices

// 将会话存储为属性以防止释放
var authSession: ASWebAuthenticationSession?

func displayWidget(widgetUrl: URL) {
authSession = ASWebAuthenticationSession(
url: widgetUrl,
callbackURLScheme: "myapp"
) { callbackURL, error in
if let error = error {
// 处理错误(用户取消等)
return
}
if let callbackURL = callbackURL {
handleWidgetCallback(callbackURL)
}
}
authSession?.presentationContextProvider = self
authSession?.start()
}

步骤4:处理回调

func handleWidgetCallback(_ callbackURL: URL) {
guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else {
return
}

let sessionId = queryItems.first(where: { $0.name == "sessionId" })?.value
let verificationId = queryItems.first(where: { $0.name == "verificationId" })?.value
let result = queryItems.first(where: { $0.name == "result" })?.value

// 根据小部件结果更新UI
if result == "PASS" {
// 处理成功流程
} else if result == "FAIL" {
// 处理失败流程
}

// 可选:使用CDK API端点在服务器端验证
if let sessionId = sessionId {
verifyResultServerSide(sessionId: sessionId)
}
}

CDK小部件类型

CDK提供多种小部件类型,每种都针对特定的合规工作流程设计:

有关每种小部件类型及其特定用例的详细信息,请参阅CDK文档中的相应指南。