如何在iOS中创建一个无缝的移动SSO(单点登录)体验

947 阅读10分钟

在iPhone上,当我们登录一个应用程序时,我们点击一个登录按钮,一个网站就会弹出来验证我们的证书。一旦验证通过,网站就会重定向到应用程序,然后你就登录了。这种熟悉的单点登录(SSO)模式经常被称为认证的重定向流程。在这个例子中,出于安全和可用性的考虑,使用网络浏览器进行认证被认为是一种 "当前最佳做法"

然而,在2017年,一个新的提示出现在登录流程中,在你被带到网站之前。下面的截图来自一个准备用Facebook登录的iOS应用。这个界面提示告诉你,一个特定的应用程序,即本例中的Yelp应用程序,想要使用一个特定的网站来登录,并警告你有关信息共享。

这个提示有几个问题:

  • 问题一(这个提示是怎么来的?*) 。许多人认为这是个糟糕的用户体验,因为这个提示看起来不合时宜,而且措辞混乱,可能会惊动终端用户。

  • 问题二(模棱两可的用户界面信息*)。如果你通过同一流程实现注销功能,提示仍然是登录,即使用户可能已经点击了注销按钮。这也让终端用户感到困惑,如下面的截图所示。这个问题被报告为AppAuth库的一个错误,尽管它被设计为一个安全和隐私功能。

在这篇文章中,我将更详细地介绍iOS平台限制的各个方面。我将解释为什么会出现这种提示,以及如何绕过它来构建一个更无缝的用户体验。

具体来说,这篇文章将

  • 解释iOS随着时间的推移所发生的演变,以及这个提示是如何和为什么被引入的。
  • 描述你在iOS上调用独立或嵌入式浏览器的各种方式,并解释cookie共享行为。
  • 讨论几种方法来消除我们在上面发现的混乱的用户体验问题。

iOS的演变简史

多年来,随着苹果公司不断增加增强用户隐私的功能,iOS设备上可用于验证的浏览器选项发生了重大变化。 为了理解iOS上提供的浏览器选项的行为,一些历史背景是有帮助的。

在iOS 9之前的认证

在2015年发布iOS 9之前,只有两种方式可以通过网络浏览器进行认证,而且都不是最佳方式:

  • UIWebView.这种方法在UIView 。移动应用程序可以拦截与UIView ,因此它是不安全的。

  • 重定向到一个外部浏览器。移动应用可以在一个单独的浏览器应用中打开网页,浏览器应用可以在认证后重定向到移动应用。然而,应用程序切换对移动用户来说不是一个好的体验。此外,如果另一个应用先注册了相同的URL方案,重定向返回可能会失败。

iOS 9(2015)中的认证

2015年,苹果公司推出了 SFSafariViewController,它在应用程序中显示一个嵌入式Safari浏览器。作为一个应用内的浏览器标签,这个界面让用户停留在应用中。一旦用户不再需要与网站互动,浏览器标签就可以被关闭和驳回。

这个解决方案是突破性的,因为它是安全的(原生应用既不能窥视嵌入式浏览器,也不能改变其状态),而且它通过避免应用切换提供了更好的用户体验。

SFSafariViewController 与独立的Safari浏览器共享cookies。这为开发者提供了一种方法,以共享cookie来实现SSO(单点登录)。

iOS 11的变化(2017年)

苹果在2017年发布的iOS 11中改变了SFSafariViewController 行为,以解决隐私问题。SFSafariViewController 不再与独立的Safari浏览器共享cookies。因此,网站不能再确定SFSafariViewController 上的用户是Safari上的同一个用户,即使视图控制器和浏览器是在同一设备上打开。

苹果公司明白,SFSafariViewController 行为的改变会破坏SSO,因为单点登录依赖于共享cookies的能力。相反,苹果不得不引入SFAuthenticationSession ,作为一种变通方法,SFAuthenticationSession ,与Safari共享一个持久的cookie。

iOS 12(2018)的迭代变化

2018年,苹果废弃了SFAuthenticationSession ,但推出了 ASWebAuthenticationSession作为一个修订的解决方案。

SFAuthenticationSessionASWebAuthenticationSession 都是专门为OAuth 2.0认证设计的,而不是用于显示一般的网页内容。为了避免滥用,当使用SFAuthenticationSessionASWebAuthenticationSession ,苹果总是显示我们之前看到的提示。该提示是为用户登录而明确设计的,并表明cookies是共享的。

苹果公司不知道ASWebAuthenticationSession 是为了登录、退出或一般的网页浏览而调用的。这就是为什么提示文本是通用的。它只说明你的应用程序正在尝试登录,而不考虑实际的使用情况,这导致了问题2中先前描述的模糊性。

iOS 13的变化(2019年)

2019年,苹果将prefersEphemeralWebBrowserSession ,作为ASWebAuthenticationSession 的一个选项。如果这个选项被设置为 "true",ASWebAuthenticationSession 不会显示上述提示,因此,它不会与Safari分享cookie。这给了开发者一个选择。要么他们获得更好的用户体验,没有令人困惑的提示和没有SSO,要么他们获得SSO,同时还有恼人的提示。

SFAuthenticationSession 或 行为ASWebAuthenticationSession

各种浏览器选项提供了不同程度的cookie共享,以限制网站追踪用户的能力。

苹果的文档中没有很好地记录cookie共享行为。SFSafariViewController doc提到,"在iOS 9和10中,它与Safari共享cookie和其他网站数据......如果你想在iOS 11及以后的应用程序和Safari之间共享数据,......使用ASWebAuthenticationSession ,而不是"。

一些第三方网站扩展了苹果的文档,强调持久性cookies在嵌入式浏览器和Safari之间共享。不幸的是,人们很少知道会话cookie也可以在不同的嵌入式浏览器之间共享。如果你的应用程序只是需要在你的移动应用程序之间进行SSO,你不必使用持久性cookie;会话cookie就足够了。

下表总结了iOS 11或更高版本中的完整共享行为。为了简洁起见,我省略了SFAuthenticationSession ,因为它已被废弃,但它的行为与ASWebAuthenticationSession 相同。我还省略了ASWebAuthenticationSessionprefersEphemeralWebBrowserSession 选项,因为当它被设置时,cookie根本就不会被共享。 我还用SVC作为SFSafariViewController 的缩写,用WAS作为ASWebAuthenticationSession 的缩写。

APP1 + SVCAPP1 + WASApp2 + SVCApp2 + WASSafari其他浏览器(例如:Chrome)。
会话cookie
持久性cookie

在下面的iOS cookie行为演示视频中,你可以看到会话cookie和持久性cookie是如何共享的。会话cookie的共享行为是不直观的。即使App1被关闭(应该清除所有的会话cookie),打开App2也会使人看到App1的会话cookie。

值得注意的是,在iOS 14及以后的版本中,你可以指定另一个浏览器,如Chrome,作为默认浏览器。这并不影响共享行为。SFSafariViewControllerASWebAuthenticationSession 总是在后台使用Safari,所以它们永远不会与Safari以外的浏览器共享cookie,即使它们被设置为默认。在线文章中经常使用的系统浏览器一词是Safari的同义词。

解决方案:如何删除额外的提示

现在我们已经回顾了iOS浏览器行为的演变,并探讨了这些变化的理由,让我们来看看改善用户体验的解决方案。

问题二的解决方案

只解决问题二--消除提示信息的模糊性--是很直接的。只使用浏览器进行登录;不要使用浏览器进行注销。你可以通过撤销访问令牌和刷新令牌直接签出你的应用程序。Okta提供了一个撤销的API,你可以直接调用。

如果你只想把用户从本地应用中签出,撤销令牌是一种解决方案。用户可能在浏览器中仍有一个登录会话,如果原生应用想再次登录,浏览器在授予访问令牌之前不会要求用户提供凭证。

如果你的网络会话可能支持许多不同的本地应用程序,这是推荐的方法。例如,FB注销特别建议不要注销网络会话。

然而,如果你需要更强的隐私和安全态势,例如银行应用,保持网络会话的活力可能不是一个选项。

问题一的解决方案

有两种潜在的方法来解决问题一。 这两种解决方案都使用不显示提示的浏览器组件。这两种方法都有一个缺点,那就是不能共享cookie,所以SSO将无法工作。如果你的应用程序需要SSO,你就必须找到一种新的方法来共享登录会话。幸运的是,Okta最近推出了本地SSO,它允许本地应用程序在没有cookie的情况下实现单点登录。请参阅我们关于移动和桌面应用程序之间的SSO的博文,了解完整的例子。下面的代码显示了如何删除提示,它假设你的应用程序不需要SSO或使用Native SSO。

首先,你可以使用prefersEphemeralWebBrowserSessionASWebAuthenticationSession 的选项。如果你使用Okta OIDC iOS库,你可以按以下方式配置noSSO 选项:

let configuration = OktaOidcConfig(with: {YourOidcConfiguration})
if #available(iOS 13.0, *) {
    configuration?.noSSO = true
}

在引擎盖下,noSSO 选项设置了prefersEphemeralWebBrowserSession 标志。注意,这个标志只在iOS 13及以上版本中可用。

其次,如果你希望支持旧的iOS版本,你可以使用SFSafariViewController 作为浏览器来呈现登录会话。下面演示了如果你使用AppAuth iOS库,如何启动SFSafariViewController

AppAuth iOS支持外部用户代理的概念,你可以使用任何浏览器来呈现登录的网页。通常情况下,你调用下面的方法来调出一个浏览器:

OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:

这个方法将调用一个默认的用户代理。或者,你可以使用下面的方法,它给你一个选项,可以插入任何外部用户代理,只要它符合OIDExternalUserAgent 协议:

OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:

已经有一个使用SFSafariViewController 作为外部代理的实现。你可以下载它并把它插入你的项目中。下面的代码片段显示了如何创建和传递一个外部用户代理OIDExternalUserAgentIOSSafariViewController:

let appDelegate = UIApplication.shared.delegate as! AppDelegate
let externalAgent = OIDExternalUserAgentIOSSafariViewController(presentingViewController: self)

appDelegate.currentAuthorizationFlow =
        OIDAuthState.authState(byPresenting: request, externalUserAgent: externalAgent) { authState, error in
    if let authState = authState {
        self.authState = authState
        print("Got authorization tokens. Access token: " +
              "\(authState.lastTokenResponse?.accessToken ?? "nil")")
    } else {
        print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
        self.authState = nil
    }
}

总结

理解iOS和Android平台级的约束是开发优秀移动体验的前提条件。我希望这篇文章能帮助你了解iOS上可用的浏览器选项以及它们的cookie共享行为。

在Okta,我们坚持优化终端用户体验。如果可能的话,每一个额外的点击都需要被消除,每一个不相干的提示都应该被避免。我们开发了像Native SSO这样的解决方案来帮助你克服移动端的限制。我希望我们的解决方案能帮助你改善你为你的终端用户所建立的体验。我们很乐意听到更多关于你的需求,以及我们如何共同建立解决方案。欢迎在下面的评论中加入你对这个解决方案或未来主题的问题和建议。

如果你喜欢阅读这篇文章,你可以通过在Twitter上关注我们和订阅我们的YouTube频道来了解我们为开发者提供的内容。