了解 Azure Active Directory (AD) 中的 OAuth2 隐式授权流Understanding the OAuth2 implicit grant flow in Azure Active Directory (AD)


Microsoft 标识平台 (v2.0)Azure Active Directory (Azure AD) 开发人员平台 (v1.0) 演变而来。Microsoft identity platform (v2.0) is an evolution of the Azure Active Directory (Azure AD) developer platform (v1.0). 开发人员可以通过它来生成应用程序,从而可以采用所有 Microsoft 标识登录,以及获取令牌来调用 Microsoft Graph 等 Microsoft API 或开发人员生成的 API。It allows developers to build applications that sign in all Microsoft identities and get tokens to call Microsoft APIs such as Microsoft Graph or APIs that developers have built. 本内容适用于版本较旧的 Azure AD v1.0 终结点。This content is for the older, Azure AD v1.0 endpoint. 建议对新项目使用 v2.0 终结点。We recommend that you use the v2.0 endpoint for new projects. 有关详细信息,请参阅为什么要更新到 Microsoft 标识平台 (v2.0)?For more info, read Why update to Microsoft identity platform (v2.0)? 以及 Microsoft 标识平台限制as well as Microsoft identity platform limitations.

OAuth2 隐式授权是 OAuth2 规范中安全疑虑最多的授权方式,因此让人诟病。The OAuth2 implicit grant is notorious for being the grant with the longest list of security concerns in the OAuth2 specification. 然而,这却是 ADAL JS 的实现方式,也是我们建议用于编写 SPA 应用程序的方法。And yet, that is the approach implemented by ADAL JS and the one we recommend when writing SPA applications. 这是怎么回事呢?What gives? 不外乎是一种权衡利弊之后的结果:事实证明,对于通过 JavaScript 从浏览器使用 Web API 的应用程序而言,隐式授权是所能找到的最好方法。It's all a matter of tradeoffs: and as it turns out, the implicit grant is the best approach you can pursue for applications that consume a Web API via JavaScript from a browser.

什么是 OAuth2 隐式授权?What is the OAuth2 implicit grant?

典型的 OAuth2 授权代码授予是使用两个单独终结点的授权授予。The quintessential OAuth2 authorization code grant is the authorization grant that uses two separate endpoints. 授权终结点用于用户交互阶段,此阶段会生成授权代码。The authorization endpoint is used for the user interaction phase, which results in an authorization code. 然后,客户端会通过令牌终结点用该代码交换访问令牌,通常还会交换刷新令牌。The token endpoint is then used by the client for exchanging the code for an access token, and often a refresh token as well. Web 应用程序必须向令牌终结点提交自身的应用程序凭据,授权服务器才能对客户端进行身份验证。Web applications are required to present their own application credentials to the token endpoint, so that the authorization server can authenticate the client.

OAuth2 隐式授权 是其他授权的变体。The OAuth2 implicit grant is a variant of other authorization grants. 它可以让客户端直接从授权终结点获取访问令牌(使用 OpenId Connect 时,还会获取 id_token),不必联系令牌终结点,也不必验证客户端。It allows a client to obtain an access token (and id_token, when using OpenId Connect) directly from the authorization endpoint, without contacting the token endpoint nor authenticating the client. 此变体专为在 Web 浏览器中运行的基于 JavaScript 的应用程序设计:在原始 OAuth2 规范中,令牌在 URI 片段中返回。This variant was designed for JavaScript based applications running in a Web browser: in the original OAuth2 specification, tokens are returned in a URI fragment. 这样,令牌位便可供客户端中的 JavaScript 代码使用,但可以保证令牌位不会包含在指向服务器的重定向中。That makes the token bits available to the JavaScript code in the client, but it guarantees they won't be included in redirects toward the server. 在 OAuth2 隐式授权中,授权终结点使用之前提供的重定向 URI 直接向客户端颁发访问令牌。In OAuth2 implicit grant, the authorization endpoint issues access tokens directly to the client using a redirect URI that was previously supplied. 另一优点是消除跨越调用的任何要求,在需要通过 JavaScript 应用程序联系令牌终结点的情况下,这些要求是必需的。It also has the advantage of eliminating any requirements for cross origin calls, which are necessary if the JavaScript application is required to contact the token endpoint.

OAuth2 隐式授权的重要特征是,此类流程绝对不会将刷新令牌返回到客户端。An important characteristic of the OAuth2 implicit grant is the fact that such flows never return refresh tokens to the client. 下一部分将说明这为何是不必要的,实际上会造成安全问题。The next section shows how this isn't necessary and would in fact be a security issue.

OAuth2 隐式授权的适用方案Suitable scenarios for the OAuth2 implicit grant

OAuth2 规范声明,设计隐式授权是为了实现用户代理应用程序,即在浏览器中执行的 JavaScript 应用程序。The OAuth2 specification declares that the implicit grant has been devised to enable user-agent applications - that is to say, JavaScript applications executing within a browser. 此类应用程序的鲜明特征是,JavaScript 代码用于访问服务器资源(通常是 Web API)以及相应地更新应用程序用户体验。The defining characteristic of such applications is that JavaScript code is used for accessing server resources (typically a Web API) and for updating the application user experience accordingly. 以 Gmail 或 Outlook Web Access 之类的应用程序为例:在收件箱中选择某封邮件时,只有邮件可视化面板会更改以显示新的选择内容,页面的其余部分保持不变。Think of applications like Gmail or Outlook Web Access: when you select a message from your inbox, only the message visualization panel changes to display the new selection, while the rest of the page remains unmodified. 此特征明显不同于传统的基于重定向的 Web 应用,在后者中,每个用户交互都会造成整页回发,并造成整页呈现新的服务器响应。This characteristic is in contrast with traditional redirect-based Web apps, where every user interaction results in a full page postback and a full page rendering of the new server response.

将基于 JavaScript 的方法发挥到极致的应用程序称为单页应用程序或 SPA。Applications that take the JavaScript based approach to its extreme are called single-page applications, or SPAs. 其思想是,这些应用程序仅提供初始 HTML 页和关联的 JavaScript,所有后续交互都由通过 JavaScript 执行的 Web API 调用驱动。The idea is that these applications only serve an initial HTML page and associated JavaScript, with all subsequent interactions being driven by Web API calls performed via JavaScript. 但是,应用程序大多是由回传驱动,但偶尔执行 JS 调用的混合方法也并非罕见;关于隐式流用法的介绍也与这些方法有关。However, hybrid approaches, where the application is mostly postback-driven but performs occasional JS calls, are not uncommon - the discussion about implicit flow usage is relevant for those as well.

基于重定向的应用程序通常通过 Cookie 确保请求的安全性,但该方法不适用于 JavaScript 应用程序。Redirect-based applications typically secure their requests via cookies, however, that approach does not work as well for JavaScript applications. Cookie 仅适用于生成 Cookie 时所针对的域,而 JavaScript 调用可能会定向到其他域。Cookies only work against the domain they have been generated for, while JavaScript calls might be directed toward other domains. 事实上,情况往往是这样的:以调用 Microsoft 图形 API、Office API、Azure API 的应用程序为例 – 这些应用程序全都位于提供应用程序的域之外。In fact, that will frequently be the case: think of applications invoking Microsoft Graph API, Office API, Azure API - all residing outside the domain from where the application is served. JavaScript 应用程序的发展趋势是完全没有后端,全部依赖于第三方 Web API 来实现其业务功能。A growing trend for JavaScript applications is to have no backend at all, relying 100% on third party Web APIs to implement their business function.

当前情况下,若要保护对 Web API 的调用,首选方法是使用 OAuth2 持有者令牌方法,该方法的每个调用都伴随 OAuth2 访问令牌的使用。Currently, the preferred method of protecting calls to a Web API is to use the OAuth2 bearer token approach, where every call is accompanied by an OAuth2 access token. Web API 会检查传入的访问令牌,如果在其中发现所需的范围,则会授予对已请求操作的访问权限。The Web API examines the incoming access token and, if it finds in it the necessary scopes, it grants access to the requested operation. 隐式流提供了方便的机制供 JavaScript 应用程序获取 Web API 的访问令牌,并提供很多与 Cookie 相关的优点:The implicit flow provides a convenient mechanism for JavaScript applications to obtain access tokens for a Web API, offering numerous advantages in respect to cookies:

  • 可以可靠地获取令牌而无需跨源调用 – 强制注册令牌要返回到的重定向 URI 可保证令牌不会转到其他位置Tokens can be reliably obtained without any need for cross origin calls - mandatory registration of the redirect URI to which tokens are return guarantees that tokens are not displaced
  • JavaScript 应用程序可以针对任意数目的目标 Web API 获取所需数目的访问令牌 – 对域没有限制JavaScript applications can obtain as many access tokens as they need, for as many Web APIs they target - with no restriction on domains
  • 会话或本地存储等 HTML5 功能可授予令牌缓存和生存期管理的完全控制权,但是 Cookie 管理对于应用而言是不透明的HTML5 features like session or local storage grant full control over token caching and lifetime management, whereas cookies management is opaque to the app
  • 访问令牌不容易遭受跨站点请求伪造 (CSRF) 攻击Access tokens aren't susceptible to Cross-site request forgery (CSRF) attacks

隐式授权流不颁发刷新令牌,这主要是出于安全考虑。The implicit grant flow does not issue refresh tokens, mostly for security reasons. 刷新令牌的范围不像访问令牌那么窄,前者授予更大的权力,因此万一泄露,会造成更大的损害。在隐式流中,令牌在 URL 中传递,因此遭到拦截的风险高于授权代码授予。A refresh token isn't as narrowly scoped as access tokens, granting far more power hence inflicting far more damage in case it is leaked out. In the implicit flow, tokens are delivered in the URL, hence the risk of interception is higher than in the authorization code grant.

不过,JavaScript 应用程序提供另一种可任其处置的机制,可用于续订访问令牌,且不会重复提示用户输入凭据。However, a JavaScript application has another mechanism at its disposal for renewing access tokens without repeatedly prompting the user for credentials. 应用程序可以使用隐藏的 iframe 来针对 Azure AD 的授权终结点执行新的令牌请求:只要浏览器仍然针对 Azure AD 域提供活动会话(读取:有会话 Cookie),则身份验证请求就可以成功且不需要用户交互。The application can use a hidden iframe to perform new token requests against the authorization endpoint of Azure AD: as long as the browser still has an active session (read: has a session cookie) against the Azure AD domain, the authentication request can successfully occur without any need for user interaction.

此模型授予 JavaScript 应用程序相关功能,以允许其独立续订访问令牌,甚至允许其为新的 API 获取新的访问令牌,前提是用户此前已表示同意。This model grants the JavaScript application the ability to independently renew access tokens and even acquire new ones for a new API (provided that the user previously consented for them). 这样可以避免因获取、维护和保护高价值项目(例如刷新令牌)而增加的负担。This avoids the added burden of acquiring, maintaining, and protecting a high value artifact such as a refresh token. 实现无提示续订的项目(Azure AD 会话 Cookie)在应用程序外部进行管理。The artifact that makes the silent renewal possible, the Azure AD session cookie, is managed outside of the application. 此方法的另一优势是,用户可以通过登录到 Azure AD 的任意应用程序从 Azure AD(在任意浏览器标签页中运行)注销。Another advantage of this approach is a user can sign out from Azure AD, using any of the applications signed into Azure AD, running in any of the browser tabs. 这样会删除 Azure AD 会话 Cookie,而 JavaScript 应用程序会自动失去为已注销用户续订令牌的功能。This results in the deletion of the Azure AD session cookie, and the JavaScript application will automatically lose the ability to renew tokens for the signed out user.

隐式授权适合我的应用吗?Is the implicit grant suitable for my app?

与其他授权相比,隐式授权具有更多风险,需要注意的方面已有详细记录(例如,在隐式流中误用访问令牌来模拟资源所有者OAuth 2.0 威胁模型和安全注意事项)。The implicit grant presents more risks than other grants, and the areas you need to pay attention to are well documented (for example, Misuse of Access Token to Impersonate Resource Owner in Implicit Flow and OAuth 2.0 Threat Model and Security Considerations). 但是,风险走势之所以较高,主要是因为它要启用执行活动代码的应用程序,并由远程资源提供给浏览器。However, the higher risk profile is largely due to the fact that it is meant to enable applications that execute active code, served by a remote resource to a browser. 如果要规划一个 SPA 体系结构,则不要设置后端组件或尝试通过 JavaScript 调用 Web API,而应使用隐式流来获取令牌。If you are planning an SPA architecture, have no backend components or intend to invoke a Web API via JavaScript, use of the implicit flow for token acquisition is recommended.

如果应用程序是本机客户端,则隐式流并不太适合。If your application is a native client, the implicit flow isn't a great fit. 在使用本机客户端的情况下,如果没有 Azure AD 会话 Cookie,应用程序无法长时间维持一个会话。The absence of the Azure AD session cookie in the context of a native client deprives your application from the means of maintaining a long lived session. 这意味着,在为新资源获取访问令牌时,应用程序会反复提示用户。Which means your application will repeatedly prompt the user when obtaining access tokens for new resources.

如果要开发包含后端的 Web 应用程序,并需要从其后端代码使用 API,则也不适合使用隐式流。If you are developing a Web application that includes a backend, and consuming an API from its backend code, the implicit flow is also not a good fit. 其他授权可以提供更强大的功能。Other grants give you far more power. 例如,授予 OAuth2 客户端凭据即可获取的令牌能够反映分配给应用程序本身的权限,这不同于用户委派。For example, the OAuth2 client credentials grant provides the ability to obtain tokens that reflect the permissions assigned to the application itself, as opposed to user delegations. 这意味着,即使在用户未积极参与某个会话这样的情况下,客户端也可始终对资源进行程序性的访问。This means the client has the ability to maintain programmatic access to resources even when a user is not actively engaged in a session, and so on. 不仅如此,此类授权还提供更严格的安全保证。Not only that, but such grants give higher security guarantees. 例如,访问令牌从不通过用户浏览器传输,因此不会有被保存在浏览器历史记录中的风险,等等。For instance, access tokens never transit through the user browser, they don't risk being saved in the browser history, and so on. 在请求令牌时,客户端应用程序还可以进行严格的身份验证。The client application can also perform strong authentication when requesting a token.

后续步骤Next steps