在与 api 不同的域上对单页应用程序进行 Auth0 身份验证

Auth0 authentication of single-page-app on a different domain than the api

我正在尝试将 Auth0 身份验证添加到我的单页应用程序。我的应用程序在一个域下 运行,例如 app.mycompany.com,而此应用程序使用的 api 在另一个域下,例如 api.mycompany.com。

我知道这个话题:

以及此处链接的 auth0 文章和 github 存储库。但我觉得我的场景稍微简单一些,因为我不一定希望在几个不同的单页应用程序之间进行单点登录。首先,我只想将 API 和应用程序分开。

这是我已经尝试过的方法:

我已经从文章 React Login With Auth0 开始,并下载了启动项目。我肯定可以毫无问题地登录,它会在我的 localStorage 中留下一个 id_token,其中包含一个由 Auth0 发布的 JWS。

我也可以直接登录 api.mycompany.com(我的 FeathersJS API 应用程序),我可以看到在 OAuth 重定向过程中,id_token 令牌神奇地转换为羽毛-jwt 令牌由我的 Feathers 应用程序颁发,其中包含与 auth0-ID 匹配的用户对象的内部 ID。我还实现了用于从 Auth0-ID 映射到我的内部 ID 的逻辑。此外,我所有的 Feathers 挂钩(例如令牌验证和用户人口)都在工作。

我想不通的是如何使用 localStorage 中的 Auth0-token 更改 app.mycompany.com 下的 react-application 运行,以便将此令牌转换为 feathers-jwt 令牌api.mycompany.com,这样所有后续的 API 调用都会自动包含 feathers-jwt 令牌,因此 API 可以验证用户和 return 正确的数据。

如有任何关于如何进行的建议,我们将不胜感激。

一些更多的背景细节:

如果您还没有这样做,您应该按照本文 (React Login with Auth0) 在您的 React 应用程序上实现身份验证。 如果您已经尝试遵循它,请用您遇到的具体问题更新您的问题。

即使您目前不需要 SSO,您的应用程序中身份验证的实际实施也不会有太大差异。通过使用 Auth0 在您的应用程序中启用 SSO,主要是启用配置开关。

最后,关于您的具体场景检查的安全相关方面背后的所有理论的完整参考:

Auth0 Architecture Scenarios: SPA + API


更新:

我链接的完整场景也涵盖了最全面的场景,其中 API 被大量客户端应用程序访问,这些应用程序甚至可能由不拥有受保护 API 的第三方开发],但想访问它背后的数据。

它通过利用目前仅在美国地区可用的最新功能来实现这一点,并且在非常高的级别上可以将其描述为作为服务提供的 OAuth 2.0 授权服务器。

您的特定场景更简单,API 和客户端应用程序都在同一个实体的控制下,因此您还有另一种选择。

选项 1 - 通过 Auth0 利用 API 授权 仅限美国地区(目前)

在这种情况下,您的客户端应用程序在身份验证时会收到一个 id_token,用于了解当前经过身份验证的用户,还会收到一个 access_token,可用于调用API 代表经过身份验证的用户。

这明确区分了客户端应用程序和 API; id_token 用于客户端应用程序使用,access_token 用于 API 使用。

它的好处是授权与身份验证明确分开,您可以通过控制访问令牌中包含的范围对授权决策进行非常细粒度的控制。

选项 2 - 在客户端应用程序和 API 中以相同的方式进行身份验证

您可以单独部署您的客户端应用程序和 API,但仍然从概念的角度将它们视为同一个应用程序(您可以在 Auth0 中配置一个客户端,同时代表客户端和 API).

这样做的好处是,您可以使用身份验证完成后获得的 id_token 来了解用户在客户端的身份,并作为对每个 API 请求进行身份验证的机制.

您必须配置 feathers API 以验证 Auth0 id_token 作为访问 API 的可接受令牌。这意味着您不会在 API 上使用任何基于身份验证的羽毛,也就是说,您只接受 Auth0 向您的应用程序颁发的令牌作为验证访问的方式。

我遇到了与您完全相同的问题,我想从单页应用程序验证用户身份,调用位于另一台服务器上的 API。

official auth0 example 是一个经典的 Express Web 应用程序,它进行身份验证并呈现 html 页面,但它不是连接到托管在其他域上的 API 的 SPA。

让我们分解一下本例中用户进行身份验证时发生的情况:

  • 用户发出请求调用 /auth/auth0 路由
  • 用户被自动重定向到 Auth0 身份验证过程(Auth0 登录表单选择提供商,然后是提供商登录屏幕)
  • 用户被重定向到 /auth/success 路由
  • /auth/success 路由重定向到静态 html 页面 public/success.html,同时发送 jwt-token 包含用户令牌的 cookie
  • 客户端,当 public/success.html 加载时,Feathers 客户端 authenticate() 方法从 cookie 中读取令牌并将其保存在本地存储中。

从现在开始,Feathers 客户端将验证从本地存储读取 cookie 的用户。

我尝试将这个场景适配到单页应用架构,实现流程如下:

  • 从 SPA,使用包含 SPA URL 的 source 查询字符串参数调用身份验证 API。例如:http://my-api.com/auth/auth0?source=http://my-spa.com
  • 服务器端,在 /auth/auth0 路由处理程序中,创建一个 cookie 来存储 URL
  • 成功登录后,读取 source cookie 将用户重定向回 SPA,在 cookie 中发送 JWT 令牌。

但是最后一步没有起作用,因为您不能在给定域(API 服务器域)上设置 cookie 并将用户重定向到其他域! (更多关于这个 here 在 Whosebug 上)

所以实际上我通过以下方式解决了这个问题:

  • 服务器端:使用 URL 哈希将令牌发送回客户端。
  • 客户端:创建一个新的 html 页面,从 URL 哈希
  • 中读取令牌

服务器端代码:

// Add a middleware to write in a cookie where the user comes from
// This cookie will be used later to redirect the user to the SPA
app.get('/auth/auth0', (req, res, next) => {
  const { origin } = req.query
  if (origin) {
    res.cookie(WEB_CLIENT_COOKIE, origin)
  } else {
    res.clearCookie(WEB_CLIENT_COOKIE)
  }
  next()
})

// Route called after a successful login
// Redirect the user to the single-page application "forwarding" the auth token
app.get('/auth/success', (req, res) => {
  const origin = req.cookies[WEB_CLIENT_COOKIE]
  if (origin) {
    // if there is a cookie that contains the URL source, redirect the user to this URL
    // and send the user's token in the URL hash
    const token = req.cookies['feathers-jwt']
    const redirectUrl = `${origin}/auth0.html#${token}`
    res.redirect(redirectUrl)
  } else {
    // otherwise send the static page on the same domain.
    res.sendFile(path.resolve(process.cwd(), 'public', 'success.html'))
  }
})

客户端,SPA 中的auth0.html 页面

在 SPA 中,我创建了一个名为 auth0.html 的新 html 页面,该页面执行 3 项操作:

  • 它从哈希中读取令牌
  • 它将它保存在本地存储中(模仿 Feathers 客户端所做的)
  • 它将用户重定向到 SPA 主页 index.html

html代码:

<html>
<body>
  <script>
  function init() {
    const token = getToken()
    if (!token) {
      console.error('No auth token found in the URL hash!')
    }
    // Save the token in the local storage
    window.localStorage.setItem('feathers-jwt', token)
    // Redirect to the single-page application
    window.location.href = '/'
  }

  // Read the token from the URL hash
  function getToken() {
    const hash = self.location.hash
    const array = /#(.*)/.exec(hash)
    if (!array) return
    return array[1]
  }

  init()
  </script>
</body>
</html>

现在在 SPA 中我可以使用 Feathers 客户端,在应用程序启动时从本地存储读取令牌。

如果有道理请告诉我,谢谢!