介绍新的Okta移动SDK(附实例)

569 阅读14分钟

多年来,Okta OIDC SDK是移动开发者用来将他们的应用与Okta集成的主要工具,但就像生活中的所有事情一样,熵是需要代价的。随着时间的推移,随着平台和语言的变化,或者新功能的出现,需要进行重构。今天我们很自豪地宣布,适用于Swift和Kotlin的Okta移动SDK现在可以使用了。

Okta最近的进展,比如对设备单点登录(SSO)的支持,设备授权许可(用于大多数流媒体智能电视应用,或没有浏览器的设备),特别是Okta身份引擎(OIE),已经被证明很难整合到老旧的OIDC SDK中。我们意识到,是时候来点新东西了。因此,在过去的几个月里,我们一直在努力开发我们的下一代SDK,针对苹果和安卓平台的本地和移动环境。

这些SDK采取了一种全新的认证方法,超越了简单的基于浏览器的登录(也被称为OAuth 2.0授权代码流)。我们采取了模块化的方法来组装SDK;你可以选择只包含构建你的应用程序所需的组件。这种架构可以实现更精简的构建,并将更多的权力和灵活性放在你的手中。它还打开了与其他SDK集成的大门,例如使用Okta身份体验(IDX)SDK的Okta身份引擎(OIE)。

为什么要替换我们目前的SDK?

首先,我想解释一下我们为什么选择替换我们目前的移动SDK。我们并不是轻易做出这个决定的,但有几个原因使之成为最佳选择:

  • 用于iOS和Android的Okta OIDC建立在一个老化的代码库上,难以维护,而且没有遵循现代移动的最佳实践。
  • OIDC库专注于基于网络的认证,这在使用本地登录时引入了开销。
  • 以移动和桌面应用为目标限制了扩展到其他平台的可能性,如智能手表、电视或应用扩展。
  • OIDC库假设一次只使用一套令牌。这一假设使开发者无法在一个应用中支持多个用户,或为不同的应用扩展或安全范围提供多个令牌。

这些是我们选择重新开始的一些关键原因,专注于方便和易用性。我们的目标是解除以前不可能实现的高级用例。

现在你已经看到了我们重构的理由,让我们来看看新的Okta Mobile SDK背后的架构和理念。

欢迎来到新的Okta移动SDK

在设计新的SDK时,我们有几个目标:

  • 改善开发者的体验,简化开发者的入职流程
  • 消除技术债务
  • 从单体架构转变为模块化架构
  • 通过灵活的扩展点释放高级功能和场景
  • 支持Okta平台的新功能

这些目标帮助我们塑造了SDK的架构,从高层次的功能到各个功能的命名。

注意:由于我主要是一名Swift工程师,我将使用Okta Mobile SDK for Swift的例子,尽管这里讨论的所有内容在我们的Okta Mobile SDK for Kotlin中也适用。

新的开发者体验之旅

"让简单的事情变得简单,让困难的事情变得可能" - Larry Wall

这个SDK的主要特点是精简的开发者体验,而不是其他。我们专注于开发者使用的API的人机工程学,确保它们有意义,并与其他类似的类型保持一致。

我们为我们所有的主要方案设定了一个 "1行到集成 "的目标。这意味着你应该能够用一行代码就能登录或退出。此外,你写的代码不应该是丢弃的。简单的解决方案应该能够成长为更复杂的解决方案,而不需要完全不同的方法。

通过将SDK模块化,使其成为相互补充的小库,这一点得到了进一步改善。简单的高层API使某些功能易于使用,但仍暴露出较低层次的细节,使你能够定制或控制SDK的行为。

模块化的架构

我们在维护OIDC SDK时面临的许多原始问题都源于它们的单体架构。这也使我们无法正确地将Okta IDX整合到应用程序中,因为在OIDC SDK中没有方便的方法来存储由IDX创建的令牌。

因此,我们将新的SDK分成了三个库,每个库都是相互建立的。AuthFoundation, OktaOAuth2, 和WebAuthenticationUI。

这种结构给了我们几个好处:

  • 通过明确的分界线,它迫使我们在依赖类之间建立干净的API合同。
  • 应用程序只需要导入必要的库来完成他们的工作,使运行时的应用程序规模更小。
  • 其他SDK(如Okta IDX)现在有了一个基础,可以使不同的工具一起工作。

每个库都有自己的责任领域。

AuthFoundation

这个库是最底层的,提供所有认证相关操作所需的通用功能:

  • 通用的OAuth 2 API客户端接口
  • 令牌存储和验证
  • JWT解析和验证
  • 钥匙链/钥匙库支持
  • 其他SDK需要的便利性和实用性

OktaOAuth2

这个库实现了Okta支持的所有主要OAuth 2流程,包括:

除了这些流程外,OktaOAuth2库还包括所有的工具和委托协议/接口,以定制每个流程的行为。

WebAuthenticationUI

当你的应用程序使用基于Web的OIDC认证时,这个库与系统的浏览器协调,以确保安全和可靠的嵌入式认证体验。由于它建立在OktaOAuth2的AuthorizationCodeFlow类之上,那些低级别的类被暴露给你,如果你的应用有特定的要求,允许广泛地定制该流程。

通过将这一逻辑分离到自己的库中,如果你使用非网络认证过程,你的应用程序可以选择不包括这个库,以消除不必要的开销。

OktaIdx

当您的应用使用Okta身份引擎(OIE)来验证您的用户时,IDX SDK(单独包含在okta-idx-swiftokta-idx-android库中)现在可以使用AuthFoundation正确地集成到这个SDK中。Okta IDX SDK也遵循与OktaOAuth2类似的模式,提供InteractionCodeFlow类。

灵活的扩展点

上面描述的定制和低级别的功能是我们主动建立在SDK中的扩展点的例子。对于SDK中的许多关键决策,这些步骤可以通过使用委托,或通过全盘替换SDK的默认值来进行定制。

您可以覆盖一切,从ID令牌的验证方式到令牌的安全存储方式,或者您的应用可以接受开箱即用的默认值。

对Okta新功能的支持

随着Okta的不断创新,引入新的功能和特性,我们的移动SDK也需要更新以利用这些功能。通过重构这些SDK,我们旨在让您尽可能容易地采用新功能,这也是我们的主要动机。让我们来看看我们现在支持的几个更令人兴奋的功能。

许多平台功能围绕着对新的认证流程的支持。传统的基于网络的登录,通过OIDC,使用OAuth 2.0授权代码流实现。我们没有把这个功能锁起来,而是把它和我们的新流程一起公开。我们尽力遵循所有认证流程的类似模式,以减少为实现一个功能所需的大量猜测工作。

新的Okta移动SDK的好处

如上所述,我们的目标不仅仅是建立与传统的OIDC SDKs相同的功能。我们希望在简化您的开发者体验的同时,释放出惊人的新功能。因此,让我们来看看我们所做的一些改进。

简化基于网络的认证

使用OIDC在网页浏览器上登录应该是一个简单的过程,这也是在移动应用中开始使用Okta的最简单方法。然而,我们觉得我们可以让它变得更加简单。

就像在Okta OIDC中一样,WebAuthenticationUI库能够使用一个名为Okta.plist 的配置文件来定义你的客户端设置。从那里,你可以使用一行代码来登录你的应用程序:

// Sign in using the default configuration
let token = try await WebAuthentication.shared?.signIn(from: view.window)

// Save the user's token
let credential = try Credential.store(token)

共享属性简化了如何创建一个WebAuthentication实例,但如果你想直接控制配置,你可以简单地在代码中创建一个:

let auth = WebAuthentication(
    issuer: URL(string: "https://dev-133337.okta.com"),
    clientId: "0oa1234567890abcdefg",
    scopes: "openid profile email offline_access",
    redirectUri: URL(string: "myapp://login"),
    logoutRedirectUri: URL(string: "myapp://logout"),
    additionalParameters: ["idp": idpName])

// Sign in using the above configuration
let token = try await auth.signIn(from: view.window)

// Save the user's tokens
let credential = try Credential.store(token)

根据你的应用程序的需要,有许多方法来配置你的客户端。请随时查看关于配置你的客户端的文档以获得更多信息。

设备授权许可

这是一个OAuth 2.0流程,用于无头设备、信息亭或文本输入有限的客户端。这方面的主要例子是在苹果电视上运行的tvOS应用程序。在这个流程中,用户会收到他们在不同设备(手机、笔记本电脑等)上输入的代码,并在第二个设备上完成登录。

上面的截图直接来自okta-mobile-swift GitHub仓库中的DeviceAuthSignIn样本

要在自己的应用程序中实现这一点,只需使用OktaOAuth2库中DeviceAuthorizationFlow,并尝试以下代码。

// Uses the client configuration in an Okta.plist
// configuration file
let flow = DeviceAuthorizationFlow()

// Get the code to display to the user
let context = try await flow.start()
showCodeToUser(context)

// Poll the server, waiting for a successful login
let token = try await flow.resume(with: context)

// Save the user's tokens
let credential = try Credential.store(token)

所有关于如何进行认证的细节,包括轮询过程、错误处理和其他注意事项,都为你处理。

设备SSO(又称令牌交换流程)

我们很高兴能够在同一设备上的一系列应用中执行单点登录,或者在同一应用中的独立应用扩展中执行单点登录。我们以前在Okta开发者博客上讨论过这个功能,但是实现设备单点登录所需的代码量让那些想尝试这个功能的开发者望而却步。

因此,通过新的TokenExchangeFlow,我们大大简化了这个过程,将其减少到只有一行的代码:

// Create the flow, using the Okta.plist configuration
let flow = TokenExchangeFlow()

// Exchange the ID and Device tokens for access tokens.
let token = try await flow.start(with: [
    .actor(type: .deviceSecret, value: deviceToken),
    .subject(type: .idToken, value: idToken)
])

// Save the user's token
let credential = try Credential.store(token)

一旦你用device_sso 范围验证了一个用户,Okta将在令牌响应中返回ID令牌和设备秘密。如果你把它保存在一个所有应用程序都可以访问的安全位置(例如,在一个共享的应用程序组中的钥匙串),你可以把这些字符串提供给TokenExchangeFlow类。

改进的令牌存储和生物识别支持

与Okta OIDC SDK一样,新的Swift SDK使用苹果钥匙链来安全地存储令牌。然而,有了新的Credential.store功能,在你存储令牌的时候就有了更多的选择。可以选择提供安全设置或开发者指定的标签来定制这些令牌的存储方式:

// Accept defaults
try Credential.store(token)

// Assign tags to later retrieve specific tokens
try Credential.store(token, tags: ["tag": "value"])

// Share the token between an app's extensions
try Credential.store(token, security: [
    .accessGroup("com.myapp.access-group")
])

// Secure the token with biometrics, and require
// the device to be unlocked
try Credential.store(token, security: [
    .accessibility(.unlocked),
    .accessControl(.biometryAny)
])

标签选项可用于帮助管理存储在钥匙链中的令牌,这有助于你的应用程序与多个令牌或账户一起工作。这是一个我非常兴奋的功能,因为它可以释放出高级的用例。让我们在下一节中讨论这个问题。

支持多个令牌/用户

我们现在支持在一个应用程序中存储多个令牌或用户账户。这比传统的Okta OIDC SDK有了很大的改进,它假设一个应用一次只能有一个用户。存储多个令牌可以帮助你创建更复杂的应用程序,例如:

  • 允许用户在配置文件之间快速切换的多用户应用
  • 利用应用程序扩展的应用程序,或出于安全原因,希望为每个扩展使用单独的令牌的小工具
  • 使用具有不同作用域的单独令牌进行升级访问的应用程序

由于一系列原因,AuthFoundation库现在支持同时存储和使用多个令牌,同时仍然支持传统的单用户应用程序,通过 Credential.default属性:

// Retrieve the credential created when storing a token
let credential = try Credential.store(token)

// Load the default credential
let credential = Credential.default

// Load a credential with a unique ID
let credential = try Credential.with(id: savedTokenId)

// Find all credentials matching a given tag
let credentials = try Credential.find(where: { metadata in
    metadata.tags["purpose"] == "background"
})

// Find all credentials containing ID token claims
let credentials = try Credential.find(where: { metadata in
    metadata.subject == userId
})

简化钥匙串处理

由于安全令牌存储大量使用苹果钥匙链,AuthFoundation库提供了一个方便的钥匙链助手,以简化钥匙链操作。我们将其公开,以简化共享秘密的存储方式,并仍然保持对你的其他应用程序或扩展的访问。例如,当保存实现TokenExchangeFlow所需的设备秘密时,你可以使用钥匙链工具来保存这些秘密,使你的其他应用程序可以访问:

if let deviceSecret = credential.token.deviceSecret,
   let idToken = credential.token.idToken
{
    try Keychain.Item(account: "ssoSecret",
                      accessGroup: "com.myapp.shared",
                      value: deviceSecret).save()
    try Keychain.Item(account: "ssoToken",
                      accessGroup: "com.myapp.shared",
                      value: idToken).save()
}

后来,检索这些值可以是一个同样直接的操作:

if let secretData = try? Keychain.Search(account: "ssoSecret").get(),
   let secretToken = try? Keychain.Search(account: "ssoToken").get(),
   let ssoSecret = String(data: secretData, encoding: .utf8),
   let ssoToken = String(data: secretData, encoding: .utf8)
{
    // Exchange the tokens using TokenExchangeFlow
}

外发请求授权

最终,开发者使用Okta Mobile SDK是为了安全地访问某个服务器上的资源。你的用户使用他们的证书进行认证,而你的应用程序可以使用由此产生的令牌代表他们执行网络请求。

由于这是一个很常见的操作,我们决定通过简化流程来改善开发者的体验:

let url = URL(string: "https://api.example.com/notes")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonBody

// Add appropriate HTTP authorization headers,
// automatically refreshing the token if needed.
await credential.authorize(&request)

let (data, response) = try await URLSession.shared
    .data(for: request)

当使用Swift Concurrency Credential.authorize()async 函数时,如果凭证的访问令牌过期,这将自动刷新,并向请求对象添加适当的 HTTP 授权头。如果你不使用Swift Concurrency,同样的事情可以通过 refreshIfNeeded(graceInterval:completion:)authorize(request:)函数来完成同样的事情。

开始使用Okta移动SDK

这些更新的目的是使建立更安全的应用程序变得更容易,同时充分使用本地平台的功能。为了帮助您开始使用,这里有一些关于您如何利用这个SDK中的新功能的建议:

  • 在你的应用程序中支持多个用户,并在账户之间快速切换。
  • 在你的应用程序中添加应用程序扩展,使用设备SSO为每个扩展创建唯一的令牌。
  • 使用设备SSO来签署你的用户在一套应用程序中。
  • 为你的用户创建不同范围的令牌,其中一个存储在生物识别技术后面,以支持你的应用程序内的升级权限(例如,需要Face ID来执行银行交易)。
  • 构建苹果电视或命令行应用程序,可以使用设备授权授予,安全地支持方便的MFA签入。
  • 简化你的工作流程,简化对你自己的服务器API的网络请求的授权,同时保持你的令牌刷新。

看看GitHub上的仓库就可以开始了。这些仓库有几个样本应用程序,展示了本文中概述的功能。仓库中引用的README和API文档是了解这些新功能的一个好方法:

如果您有问题,或者想分享您对这个新SDK的看法,请在下面的评论中告诉我们。