跳到主要内容

移动端实现指南

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

概述

AgeKit+小部件是从API调用返回的URL,包含完整的年龄验证界面。将这些小部件集成到移动应用程序时,您有多种显示选项,每种选项都有不同的功能和权衡。主要考虑因素包括:

  • 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://verification-complete
  2. 调用API时将深度链接作为redirectUrl包含
  3. 小部件完成后重定向到您的深度链接
  4. 应用处理深度链接并提取结果
备注

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

回调URL参数

当小部件重定向到您的回调URL时,它包含以下查询参数:

  • verificationId:此验证的唯一标识符
  • resultPASSFAIL

回调URL示例:

myapp://verification-complete?verificationId=7854909b-9124-4bed-9282-24b44c4a3c97&result=PASS

实现回调URL

调用AgeKit+ API时,在options对象中包含redirectUrl。URL可以是:

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

有关redirectUrl参数的更多详细信息,请参阅瀑布流指南

DOM消息(仅限WebView/WKWebView)

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

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

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/age-verification/perform-access-age-verification") 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",
"criteria": [
"ageCategory": "DIGITAL_YOUTH_OR_ADULT"
],
"options": [
"redirectUrl": "myapp://verification-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 {
handleVerificationCallback(callbackURL)
}
}
authSession?.presentationContextProvider = self
authSession?.start()
}

步骤4:处理回调

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

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" {
// 处理验证失败
}

// 可选:在服务器端验证
if let verificationId = verificationId {
verifyResultServerSide(verificationId: verificationId)
}
}
最佳实践

为了安全性和数据完整性,始终使用/age-verification/get-status端点在服务器端验证验证结果,而不是仅依赖客户端数据。