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

时间:2016-11-08 22:31:17

标签: node.js express auth0 feathersjs

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

我知道这个帖子:

Single Sign On (SSO) solution/architecture for Single Page App (SPA)

和auth0文章和github存储库在这里链接。但我觉得我的方案稍微简单一些,因为我不一定要在几个不同的单页应用程序之间进行单点登录。首先,我只想要API和应用程序之间的分离。

以下是我已经尝试过的内容:

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

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

我无法弄清楚如何使用localStorage中的Auth0令牌更改app.mycompany.com下运行的react-application,以便api.mycompany.com将此令牌转换为feathers-jwt令牌,以这种方式,所有后续API调用自动包含feathers-jwt标记,以便API可以验证用户并返回正确的数据。

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

更多背景细节:

  • api建立在node.js和featherjs上(基本上是Express的扩展)
  • 单页面应用程序构建在ReactJS上,由一个简单的Express服务器提供服务,但它可以由任何可以通过http提供静态文件的服务器提供服务。单页面应用程序向api发出http请求以读取数据并执行操作。
  • api有以下代码行负责身份验证:

    const authentication = require('feathers-authentication');
    const Auth0Strategy = require('passport-auth0').Strategy;
    app.configure(authentication({
        local:false,
        token: {
          secret: 'mysecret',
          payload: ['email', 'auth0Nickname'],
          issuer: 'mycompany'
        },
        idField: 'id',
        shouldSetupSuccessRoute: false,
        auth0: {
          strategy: Auth0Strategy,
          domain: 'mycompany.eu.auth0.com',
          'clientID': 'xxx',
          'clientSecret': 'yyy'
        }
    }));
    

2 个答案:

答案 0 :(得分:2)

我遇到与您完全相同的问题,我想从单个页面应用程序验证用户,调用位于其他服务器上的API。

official auth0 example是一个经典的Express网络应用程序,可以进行身份​​验证并呈现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路由处理程序中,创建一个用于存储该URL的cookie
  • 成功登录后,请阅读source Cookie以将用户重定向回SPA,并在Cookie中发送JWT令牌。

但最后一步不起作用,因为您无法在给定域(API服务器域)上设置cookie并将用户重定向到其他域! (更多关于Stackoverflow上的here

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

  • 服务器端:使用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客户端,在应用程序启动时从本地存储中读取令牌。

让我知道它是否有意义,谢谢!

答案 1 :(得分:1)

如果您还没有这样做,则应遵循本文(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的id_token代表经过身份验证的用户。

这使客户端应用程序和API之间有明确的区别; access_token用于客户端应用程序使用,id_token用于API用途。

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

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

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

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

您必须配置Featles API以验证Auth0 pkey (PKey)作为访问API的接受令牌。这意味着您不会根据API上的身份验证使用任何羽毛,也就是说,您只需接受Auth0颁发给您的应用程序的令牌作为验证访问权限的方式。