我应该将身份验证代码(用于将访问令牌交换)移动到用于Web API / SPA OpenID Connect实现的API吗?

时间:2019-02-02 20:36:20

标签: oauth-2.0 identityserver4 openid-connect

我正在努力建立用于SSO身份验证的OpenID Connect服务器。我想我的基本设置/要求是非常标准的,但我有一点点困难,把他们放在一起。

广泛的设置是一个单页应用程序,一个Web API和一个身份服务器。 SPA使用与Web API相同的域名提供服务,并且ID服务器位于不同的域中,因此我可能有几种SPA / Web API组合,但是当然每种情况都是相同的设置(具有静态内容的单个主机和API)。目前,我正在与IdentityServer4合作创建身份服务器;如果该提供商存在某种问题,我很乐意尝试其他提供商,但到目前为止效果很好。

我认为我的登录要求也很标准;我想拥有短暂的访问令牌,并且我还想使用刷新令牌来实现滑动到期,因此不必将用户重定向到我的SPA之外,直到他们在一段时间内处于非活动状态为止(但是我结束了向上限定)。

经过一些研究,我想我想要使用授权码流程。因此,总的来说,我认为可行的方式是:

  1. 用户访问应用主机(即提供网页API和SPA);静态SPA供应
  2. SPA加载并确定本地存储中没有访问令牌。 SPA通过生成随机标识符并将其存储在会话存储中来启动登录过程,然后将浏览器导航到ID服务器主机
  3. 用户通过ID服务器主机进行身份验证
  4. ID服务器托管重定向到客户端,并在重定向中包含最初生成的SPA和授权码的随机标识符
  5. 在加载并检测到它具有访问代码后,SPA会在会话存储中检查步骤2中存储的标识符。找到它后,SPA会调用Web API来交换访问令牌的授权代码
  6. 腹板API使用反向信道与ID服务器以产生访问令牌和刷新令牌
  7. Web API存储刷新令牌和访问令牌,然后将访问令牌颁发给客户端
  8. 在今后所有申请,客户端使用与Web API令牌的访问。当SPA确定令牌已过期或即将到期,它以某种方式请求刷新访问(我要去手波的刷新位现在)

所以我经历了tutorial on the IdentityServer4 site,令我惊讶的是,我最终处于一种不同的状态。我花了一些时间来解决它;我要说的是,要有人遵循的步骤是“添加JavaScript客户端”,但我愿意成为在实现OpenID Connect的人们中常见的结果。最终的流程与我从步骤5开始的预期有所不同;代替SPA调用带有授权代码的网络API并请求访问令牌,则SPA用途CORS并使得跨域请求回ID服务器以请求令牌接入。本教程并没有真正涵盖所有刷新令牌(文档的其他部分都做了,但只是简短地介绍了),但是我认为这意味着,如果我想使用刷新令牌,则会将它们发行给客户端,并且它会使用本地存储来存储他们。那么对于未来的刷新,也愿意做一个跨域请求回ID服务器。附带说明一下,另一个令人惊讶的地方是该教程使用了PKCE,但对于Web应用程序而言,在研究中似乎没有必要使用PKCE。这有点重要,因为在客户端中包含SHA-2实施会增加我的应用程序的大小。

我相信这是一个不好的做法,发出令牌网络客户端刷新,并要求它来存储它;对于可能出现的特定漏洞,我有些含糊,但总体思路是,如果有人以某种方式破坏了您的客户端,则刷新令牌要比短期访问令牌强大得多。

因此,我想办法解决这个问题,我相信本来可以起作用的方式是,Web API是OAuth 2的“依赖方”,而本教程将其设置为使客户端是“依赖方”。这让我认为,如果我想让到期日滑落,我必须走过教程的去处,然后像我最初设想的那样,将用于令牌交换的功能从客户端转移到Web API中。最终看起来就像Web API在功能上是SPA的代理,以交换访问令牌的授权代码。

最终,我的问题是:我说对了吗?看起来,实际上有两种不同的模型可以为SPA / API Web应用程序实现OpenID Connect。一个是API是RP,另一个是SPA是RP。如果你想使用刷新令牌,我想你应该选择1走,但也许如果你关心的是,API可以模拟你与选项2去客户端?对我来说,这似乎仍然无关紧要。授权代码/访问令牌交换只能用于特定的应用程序,因此这并不像一个API可以突然将其身份验证为该设置中的另一个后端。我很担心自己不能从结构上改变教程的设置,因为这与安全性有关。

更新

尽管答案被接受,但我还是使用授权代码流而不是隐式流,因为这是IETF的最新建议(请参阅https://tools.ietf.org/html/draft-parecki-oauth-browser-based-apps-02#section-4,并在https://brockallen.com/2019/01/03/the-state-of-the-implicit-flow-in-oauth2/上有很多文章)。我接受了这个答案,因为通过iframe使用无提示刷新而不是刷新令牌似乎是我尝试执行的最标准的方法。使用它,我能够构建一个类似于本教程的工作系统。事实上,客户端库它建议(oidc-client)有一个内置的函数来处理细节。为了完整起见,我要从头开始使用这个服务:

import oidc from "oidc-client";
import Url from "url-parse";

let baseUrl = new Url(window.location.href).set("pathname", "").set("query", "").set("hash", "");
let redirectUrl = (new Url(baseUrl)).set("query", "redirect=fromIdentityProvider");
let silentRedirectUrl = (new Url(baseUrl)).set("pathname", "silent-refresh.html");

let identitySettings = {
  authority: "[my application's id server domain]",
  client_id: "[my client's id]",
  redirect_uri: redirectUrl.toString(),
  response_type: "code",
  scope: "openid profile [my application's resource name]",
  post_logout_redirect_uri: baseUrl,
  automaticSilentRenew: true,
  silent_redirect_uri: silentRedirectUrl.toString()
};

let userManager = new oidc.UserManager(identitySettings);
let user = null;

export default {
  async logIn() {
    await userManager.signinRedirect();
  },
  async isLoggedIn() {
    return !!(await this.getAccessToken());
  },
  async logOut() {
    await userManager.signoutRedirect();
  },
  async getAccessToken() {
    user = await userManager.getUser();
    return user ? user.access_token : null;
  },
  async initializeApp() {
    let url = new Url(window.location.href, true);

    if (url.query && url.query.redirect === "fromIdentityProvider") {
      await new oidc.UserManager({
        response_mode: "query"
      }).signinRedirectCallback();
      window.location = "/";
      return false;
    }
    user = await userManager.getUser();
    return true;
  }
};

然后在我的应用程序调用initializeApp应用程序启动和getAccessToken时之前的任何API调用。我还需要最终添加从API上401自动重定向的能力,但是这是很容易的。

要进行无声重定向工作,我创建基于这里的说明无声-将redirect.html:https://www.scottbrady91.com/OpenID-Connect/Silent-Refresh-Refreshing-Access-Tokens-when-using-the-Implicit-Flow。我还集成了Google身份验证作为外部提供程序,并验证了它也可用于无提示刷新,因此没有折衷办法。

为解决这个问题,对我来说,我原来的问题的答案基本上是“否”,我不想将交换步骤移到后端。我确实也决定使用PKCE,即使在我看来它不是必需的,这是我提到的IETF建议中的内容,因此我会坚持下去。

1 个答案:

答案 0 :(得分:4)

SPA有一个特殊的OAuth2流-Implicit grant。如果只需要访问令牌,则在访问&response_type=token端点时指定/auth。或者,你可以要求使用令牌和一个ID &response_type=token id_token&scope=openid。 SPA从自动化提供者(在哈希部分#access_token=...中)在重定向URL中获取令牌及其生命周期expires_in=...。因此令牌保留在您的浏览器中-哈希部分不会发送到托管SPA文件的服务器。

您的SPA应该验证并保留这两个值,并且在令牌到期之前,应使用&prompt=none参数在/auth中调用iframe端点。如果您授权提供支持单点登录(SSO),那么你应该得到,而用户注意到它令牌的新鲜的访问。因此,它的工作原理类似于刷新令牌,而无需CORS,PKCE或客户机密。

如果你想实现一些更复杂的SSO管理,看看在OpenID Connect Session management RFC。