与单页应用程序一样,移动应用程序也无法维护客户机密。因此,移动应用程序还必须使用不需要客户端密码的 OAuth 流程。当前的最佳做法是将授权流程与 PKCE 一起使用,同时启动外部浏览器,以确保本机应用程序无法修改浏览器窗口或检查内容。
许多网站都提供移动 SDK 来为您处理授权过程。对于这些服务,您最好直接使用他们的 SDK,因为他们可能已经通过非标准添加来扩充了他们的 API。Google 提供了一个名为 AppAuth 的开源库,它处理下述流程的实现细节。它意味着能够与任何实现规范的 OAuth 2.0 服务器一起工作。如果服务不提供自己的抽象,而您必须直接使用它们的 OAuth 2.0 端点,本节介绍如何使用授权代码流和 PKCE 来与 API 交互。
创建一个“登录”按钮,该按钮将在应用程序中打开一个安全的网络浏览器(ASWebAuthenticationSession
或SFSafariViewController
在 iOS 上,以及在 Android 上的“自定义选项卡”)。您将为授权请求使用相同的参数,如服务器端应用程序中所述,包括 PKCE 参数。
生成的重定向将包含临时授权代码,应用程序将使用该代码从其本机代码交换访问令牌。
在此示例中,我们将介绍一个简单的 iPhone 应用程序,该应用程序获得访问虚构 API 的授权。
要开始授权过程,应用程序应该有一个“登录”按钮。该链接应构建为服务授权端点的完整 URL。
客户端首先创建所谓的 PKCE“代码验证器”。这是一个加密随机字符串,使用字符A-Z
、a-z
、0-9
和标点字符-._~
(连字符、句点、下划线和波浪号),长度在 43 到 128 个字符之间。
一旦应用程序生成了代码验证器,它就会使用它来创建code challenge。代码质询是代码验证器的 SHA256 散列的 Base64-URL 编码字符串。该散列值在授权请求中发送,因此原始随机字符串永远不会暴露给应用程序外部的任何内容。
授权请求参数用于创建授权URL,例如:
https://authorization-server.com/authorize
?client_id=eKNjzFFjH9A1ysYd
&response_type=code
&redirect_uri=com.example.app://auth
&state=1234zyx
&scope=photos
&code_challenge=hKpKupTM381pE10yfQiorMxXarRKAHRhTfH_xkGf7U4
&code_challenge_method=S256
在这种情况下请注意重定向 URL 的自定义方案。iOS 和 Android 都为应用程序提供注册自定义 URL 方案的能力,这些方案可用作重定向 URL。这有时在平台文档中也称为“深度链接”。这两个平台还允许应用程序注册自己,以便在访问匹配的 URL 模式时启动(iOS 上的“通用链接”和安卓上的“应用程序链接”)。这两种方法在使用应用程序时提供大致相同的体验,但“通用/应用程序链接”方法在用户未安装应用程序的情况下访问 URL 时提供更好的回退行为。“Universal Links”和“App Links”方法通常被认为更现代,可能是您今后应该使用的方法。
当用户点击“登录”按钮时,应用程序应在安全的应用程序内浏览器(ASWebAuthenticationSession
在 iOS 上,或在 Android 上的“自定义选项卡”)中打开授权 URL。在应用程序中使用嵌入式WebView
窗口被认为是极其危险的,因为这无法保证用户正在查看该服务自己的网站,因此很容易成为网络钓鱼Attack的来源。嵌入式 Web 视图还提供更差的用户体验,因为它不共享系统 cookie,并且用户将始终必须输入他们的凭据。通过使用与系统浏览器共享 cookie 的平台安全浏览器 API,您的优势在于用户可能已经登录到该服务,并且不需要每次都输入他们的凭据。
在被定向到 auth 服务器后,用户会看到如下所示的授权请求。
当用户完成登录时,该服务将重定向回您的应用程序的重定向 URL,这将导致安全浏览器 API 将生成的 URL 发送到您的应用程序。重定向的标Location
头将类似于以下内容,它将传递给您的应用程序。
com.example.app://auth://auth?state=1234zyx
&code=lS0KgilpRsT07qT_iMOg9bBSaWqODC1g061nSLsa8gV2GYtyynB6A
然后,您的应用程序应该从 URL 中解析出状态值和授权代码,验证状态是否与它设置的值相匹配,然后将授权代码交换为访问令牌。
为了交换访问令牌的授权代码,应用程序向服务的令牌端点发出 POST 请求。这是从应用程序的本机代码而不是从浏览器内部发生的,因为这是存储 PKCE code_verifier 的地方。该请求将具有以下参数。
grant_type
(必需的)该grant_type
参数必须设置为“ authorization_code
”。
code
(必需的)此参数用于从授权服务器接收到的授权代码,该代码将包含在该请求的查询字符串参数“code”中。
redirect_uri
(可能需要)如果重定向 URL 包含在初始授权请求中,则它也必须包含在令牌请求中,并且必须相同。有些服务支持注册多个重定向 URL,有些服务需要在每个请求中指定重定向 URL。查看服务的文档以了解详细信息。
code_verifier
(必需的)由于客户端code_challenge
在初始请求中包含一个参数,它现在必须通过在 POST 请求中发送它来证明它具有用于生成哈希的秘密。这是用于计算先前在code_challenge
参数中发送的哈希值的明文字符串。
尽管此流程中未使用客户端密码,但请求需要发送客户端 ID 以识别发出请求的应用程序。这意味着客户端必须将客户端 ID 作为 POST 主体参数包含在内,而不是像在包含客户端机密时那样使用 HTTP 基本身份验证。
POST /oauth/token HTTP/1.1
Host: authorization-endpoint.com
grant_type=code
&code=Yzk5ZDczMzRlNDEwY
&redirect_uri=com.example.app://auth
&client_id=eKNjzFFjH9A1ysYd
&code_verifier=Th7UHJdLswIYQxwSg29DbK1a_d9o41uNMTRmuH0PM8zyoMAQ
应用程序在平台上使用适当的浏览器 API 而不是使用嵌入式 Web 视图至关重要。在 iOS 上,这是ASWebAuthenticationSession
或SFSafariViewController
,在 Android 上,这被称为“自定义标签”。
使用嵌入式 Web 视图有很多缺点,导致用户更有可能陷入网络钓鱼Attack,因为它无法让用户验证他们正在查看的网页的来源。Attack者可以轻松创建一个看起来像授权网页的网页并将其嵌入到他们自己的恶意应用程序中,从而使他们能够窃取用户名和密码。
在用户体验方面,使用嵌入式 Web 视图也有 Web 视图不共享系统 cookie 的缺点,因此用户每次都将被迫输入他们的凭据。相反,如果用户已经在其浏览器中登录到授权服务器,则使用适当的安全浏览器 API 将为用户提供绕过在应用程序中输入其凭据的机会。