DeviceAuthenticator 1.1.1

DeviceAuthenticator 1.1.1

由以下人员维护:IldarMoises OlmedoSteve Lind



 
依赖
GRDB.swift~> 5
OktaJWT~> 2.3
OktaLogger/FileLogger~> 1
 

  • Okta 开发者

Okta 设备 SDK

使您的应用能够验证使用 Apple 推送通知服务 (APNs) 的 Okta 验证器的用户身份。

目录

发布状态

此库使用语义版本控制,并遵循 Okta 的 库版本策略

最新版本可以在 发布页面 上找到。

需要帮助?

如果您在使用 SDK 时遇到问题,可以

入门

为了使用此 SDK,您需要在您的 Okta 服务中创建一个自定义验证器,并提供您的推送通知凭证。
有关更多详细信息,请参见 自定义验证器集成指南

包括 Okta 设备 SDK

Cocoapods

Okta 设备 SDK 可从 CocoaPods 获取。要将它添加到您的项目中,请在 Podfile 中添加以下行

target 'MyApplicationTarget' do
  pod 'DeviceAuthenticator'
end

Swift 包管理器

此 SDK 可通过 Swift 包管理器获取。要安装它,可以从以下 URL 导入它

https://github.com/okta/okta-devices-swift.git

注意:此 SDK 仅供 iOS 平台。不支持 MacOS 和 WatchOS。

用法

Okta 设备 SDK 支持在 Okta org 中使用自定义验证器进行身份验证。您的应用程序以三种方式与该自定义验证器交互

  • 注册:将设备和可选生物识别数据添加到用户的帐户中,使用推送通知启用身份验证。
  • 验证:通过提示用户批准或拒绝登录尝试来验证用户身份。
  • 更新:更新用户帐户中的生物识别数据,刷新 APNs 令牌以保持其活跃状态,并从用户的帐户中删除设备。

创建

首先创建一个用于与设备 SDK 交互的设备验证器。

let appicationConfig = ApplicationConfig(applicationName: "TestApp",
                                         applicationVersion: "1.0.0",
                                         applicationGroupId: "group.com.company.testapp")
#if DEBUG
appicationConfig.apsEnvironment = .development
#endif

let authenticator = try? DeviceAuthenticatorBuilder(applicationConfig: applicationConfig).create()

注册

为用户帐户注册推送验证方法

let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
let apnsToken = <ab12ef7b 32b...> // from `application:didRegisterForRemoteNotificationsWithDeviceToken`
let enrollmentParameters = EnrollmentParameters(deviceToken: apnsToken, enableUserVerification: false)
let authenticatorConfig = AuthenticatorConfig(orgURL: URL(string: "atko.okta.com")!,
                                              oidcClientId: "client_id")

authenticator.enroll(authenticationToken: AuthToken.bearer(accessToken),
                     authenticatorConfig: authenticatorConfig,
                     enrollmentParameters: enrollmentParameters) { result in
                            switch result {
                                case .success(let enrollment):
                                  print("Enrollment created: \(enrollment)")       
                                case .failure(let error):
                                  print(error.localizedDescription)
                                }                          
                          }
                           

检索现有注册

为了检索现有注册的信息,请使用 allEnrollments()。这可以用于显示帐户列表的属性或查找特定帐户以进行更新或删除。

let enrollments = authenticator.allEnrollments()

更新推送令牌

每当iOS分配或更新推送令牌时,您的应用程序必须将新的deviceToken传递给SDK,SDK将为此设备所有注册的申请进行更新。

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
    let enrollments = authenticator.allEnrollments()
    enrollments.forEach { enrollment in
        enrollment.updateDeviceToken(deviceToken, authenticationToken: AuthToken.bearer(accessToken)) { error in
            if let error = error {
                print("Error updating APNS token: \(error)")
            }
        }
    }
}

将用户身份验证能力添加到现有注册

用户可能会被提示进行生物识别本地身份验证以应对挑战推送因素;如果认证策略要求用户验证,则会发生这种情况。

let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
let enrollments = authenticator.allEnrollments()
enrollments.forEach { enrollment in
    enrollment.setUserVerification(authenticationToken: AuthToken.bearer(accessToken), enable: true) { error in
        if let error = error {
            print("Error enabling user verification: \(error)")
        }
    }
}

启用使用您的应用程序进行客户端初始化的后台通道身份验证(CIBA)

启用您的应用程序以响应由Okta后端服务器发送的CIBA授权挑战。CIBA挑战默认禁用。下面的代码展示了如何为您的应用程序每个注册的自定义身份验证器启用挑战。

let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
let enrollments = authenticator.allEnrollments()
enrollments.forEach { enrollment in
    enrollment.enableCIBATransactions(authenticationToken: AuthToken.bearer(accessToken, enable: true) { error in
        if let error = error {
            print("Error enabling support for CIBA transactions: \(error)")
        }
    } 
}

删除注册

使用enrollment.delete()方法取消推送验证的注册。当从Okta服务器收到成功的响应时,SDK将从设备中删除注册。

let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
let enrollments = authenticator.allEnrollments()
enrollments.forEach { enrollment in
    enrollment.delete(enrollment: enrollment, authenticationToken: AuthToken.bearer(accessToken)) { error in
        if let error = error {
            print("Error deleting enrollment: \(error)")
        }
    }
}

从设备删除注册

使用enrollment.deleteFromDevice()方法从设备中删除注册,而不通知Okta服务器。调用deleteFromDevice与delete之间的区别在于deleteFromDevice不会发出服务器调用以取消推送验证的注册,因此它不需要任何授权。

let accessToken = "eySBDC...." // https://developer.okta.com/docs/reference/api/oidc/#access-token
let enrollments = authenticator.allEnrollments()
enrollments.forEach { enrollment in
    do {
        try enrollment.deleteFromDevice()
    } catch {
        print("Error deleting enrollment: \(error)")
    }
}

验证

当用户尝试登录已注册的帐户时(例如,通过应用程序或网络浏览器),Okta的后端将创建一个推送挑战,并使用上传到您的okta控制台的用户API令牌通过APNs将此挑战发送给所有注册的设备。

在通过Okta管理员门户启用有效的APNs配置的情况下,推送挑战将通过UNUserNotificationCenter以与其他推送通知可能提供给应用程序相同的方式交付。

应用程序在前台

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            willPresentNotification notification: UNNotification,
                            withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    // Try to parse incoming push notification
    if let pushChallenge = try? authenticator.parsePushNotification(notification) {
        // This is Okta push challenge. Handle the push challenge
        pushChallenge.resolve(onRemediationStep: { step in
                                 self.handle(step)
                          }) { error in
                                 if let error = error {
                                    print("Error resolving challenge: \(error)")
                               }
                          }
        }
        return
    }
    
    // handle non-okta push notification responses here
    completion([])
}

按需检索挑战

尽管APNs消息通常快速交付,但用户设备有时可能不会及时收到。此外,用户可能以阻止显示通知的方式配置了应用程序的通知权限。

为了适应这些场景,SDK提供了一种基于拉取的API来获取待处理的挑战。这允许应用程序在预期接收挑战时轮询挑战(例如,用户尝试使用您的应用程序登录)。

需要对每个注册的注册调用此API - 如果成功,回调将返回包含登记中待处理的挑战数组的数组。

func retrievePushChallenges() {
    let enrollments = authenticator.allEnrollments()
    enrollments.forEach { enrollment in
        enrollment.retrievePushChallenges(authenticationToken: AuthToken.bearer("accessToken")) { result in
            switch result {
                case .success(let challenges):
                    print("Challenges retrieve: \(challenges)")      
                case .failure(let error):
                    print(error.localizedDescription)
              }
        }
    }
}

// App may choose to execute this operation upon app foreground, for example
func applicationDidBecomeActive(_ application: UIApplication) {
    retrievePushChallenges()
}

解决挑战

通过上述任何一个渠道收到挑战后,您的应用程序应

忽略
这些挑战以继续登录。为了完成解决,SDK可能请求纠正步骤,例如RemediationStepUserConsent(请求用户批准/拒绝挑战)

成功或失败后,将调用带有可选的Error对象的completion关闭。

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            willPresentNotification notification: UNNotification,
                            withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if let pushChallenge = try? authenticator.parsePushNotification(notification) {
        // Handle the push challenge
        pushChallenge.resolve(onRemediationStep: { step in
                                 self.handle(step)
                          }) { error in
                                 if let error = error {
                                    print("Error resolving challenge: \(error)")
                               }
                          }
        }
        return
    }
    
    // handle non-okta push notification responses here
    completion([])
}

func handle(_ remediationStep: RemediationStep) {
    switch remediationStep {
    case let consentStep as RemediationStepUserConsent:
        // This challenge requires user consent to be processed.
        // Show UX to allow the user to say "yes" or "no" to the sign-in attempt, then provide their response.
        consentStep.provide(.approved)
    case let messageStep as RemediationStepMessage:
        // There is a non-fatal error happened during challenge verification flow - for example user verification key is not available
        print(messageStep.message)
    default:
        // Default processing for unexpected remediation step
        remediationStep.defaultProcess()
    }
}

有关解决推送挑战的完整实现的示例,请参阅推送示例应用程序

访问令牌管理

SDK使用HTTPS协议与Okta服务器通信,并需要访问令牌来进行用户身份验证和授权。对于身份验证流程和访问令牌请求,请使用Okta Swift移动SDK的最新版本。为推送验证器注册,用户需要包含okta.myAccount.appAuthenticator.manage范围的访问令牌。您还可以为此操作使用此范围:

  • 注册和注销用户验证密钥
  • 更新推送验证器注册的设备令牌
  • 请求挂起的推送挑战
  • 启用和禁用推送验证器注册的CIBA功能
  • 删除推送验证器注册

注意:使用敏感数据的应用程序不应存储或缓存包含okta.myAccount.appAuthenticator.manage范围的访问令牌或刷新访问令牌。相反,应重新验证用户并获得新的访问令牌。高风险操作包括以下内容

  • 注册推送验证器
  • 启用或禁用推送验证器注册的用户验证
  • 删除推送验证器注册

其他操作风险较低,可能不需要交互式身份验证。因此,Okta Push SDK 实现了静默用户重新验证 API retrieveMaintenanceToken。通过获取维护访问令牌,应用程序可以静默执行以下操作

  • 请求挂起的推送挑战
  • 启用和禁用推送验证器注册的 CIBA 功能
  • 更新推送验证器注册的设备令牌

使用示例

func retrievePushChallenges() {
    let enrollments = authenticator.allEnrollments()
    enrollments.forEach { enrollment in
        enrollment.retrieveMaintenanceToken() { result in
            switch result {
            case .success(let credential):
                let authToken = AuthToken.bearer(credential.access_token)
                enrollment.retrievePushChallenges(authenticationToken: authToken) { result in
                    switch result {
                    case .success(let challenges):
                        print("Challenges retrieve: \(challenges)")      
                    case .failure(let error):
                        print(error.localizedDescription)
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
}

已知问题

截至 iOS 16,苹果要求有授权才能读取用户设备的当前名称。如果没有这个权限,Okta 最终用户仪表板和管理员的设备页面将显示 'iPhone' 或 'iPad' 而不是用户输入的名称。您的宿主应用程序将需要在这个权限可用时请求授权

贡献

我们很高兴接受贡献和 PR!请参阅贡献指南了解如何构建贡献。