Javascript中的安全OAuth

Secure OAuth in Javascript

本文关键字:OAuth 安全 Javascript      更新时间:2023-09-26

我有一个api,它使用OAuth 1.0a对使用它的应用程序进行身份验证。它取代了一个旧的api,该api使用了许多不推荐使用的自定义构建和大杂烩调用。

众所周知,OAuth 1.0a在(客户端)Javascript中是不安全的,因为它依赖于消费者的秘密。这是不可能的,因为源总是可见的。

我们有Chrome、Firefox、IE和Safari的浏览器扩展,将来需要使用此api。这些扩展都主要或完全用Javascript编写,因此存在安全问题。

这些扩展是内部的,因此可以使用自定义身份验证方法来获取其访问令牌。

我计划实施以下内容:

  • 用户在浏览器中登录到网站
  • 网站会向他们发送一个带有会话密钥的cookie
  • 然后,我们的扩展获取cookie并将其传递给api
  • api验证它是一个有效的&活动会话,并向扩展发布其访问令牌
  • 这些代币在到期前最多可使用一个小时
  • javascript发布的cookie也将有较低的费率限制

它在以下假设下运行:

  • 如果另一个应用程序可以访问你的cookie,那么他们无论如何都可以在网站上模拟你,所以访问api也没什么不同
  • 所有身份验证方法仍然通过我们的控制
  • 代币的定期到期意味着,如果它们被泄露,那么利用的时间是有限的

我的问题是,这是一种限制访问api的安全方法吗?有更好的吗?

几个注意事项我知道一个事实,chrome扩展可以请求权限访问您的cookie为一个给定的网站。我相信firefox扩展也可以做到这一点。

显然,我们不希望我们的cookie在任何页面上都可以通过javascript访问,否则我们会面临XSS攻击,所以它们只需要通过扩展即可访问。

我写了一个网站,通过OAuth的javascript库进行OAuth登录。这就是工作流程:

  1. 只有具有LocalStorage的浏览器才支持OAuth
  2. 登录表单将检查LocalStorage中的OAuth密钥,并在存在OAuth键的情况下自动尝试OAuth登录
  3. 登录表单上有一个"记住我"复选框,因此用户可以在登录时为其创建OAuth令牌
  4. 成功登录并记住我将:
    • 查找或创建名称等于User Agent的ClientApplication,并在必要时创建令牌
    • 在HTML响应中使用javascript标记进行响应。javascript标记将使用作为参数传递的标记来调用javascript函数。此函数将OAuth令牌保存到LocalStorage
  5. OAuth登录尝试失败将:
    • 在HTML响应中使用javascript标记进行响应。javascript标记将调用一个javascript函数来清除OAuth令牌的LocalStorage设置。这将阻止其他OAuth登录尝试

这个过程还有更多的细节,如果你想的话,我可以告诉你更多。

对于那些后来来到这篇文章的人来说,只有一些想法:

  1. "这样安全吗"->这取决于我们要保护哪些威胁。我将在以下几点中假设,该解决方案已经暗示了一个可信的网络链接(以防止在传输中的令牌或凭据拦截尝试)。然而,描述中缺少一个关键元素,因为它没有提到我们是保护API不受未经授权的用户(人类)的影响,还是不受未授权的API客户端的影响(比如在浏览器中运行的恶意扩展)。前者可以通过可用的开放标准很容易地实现,而人们应该忘记试图阻止未经授权的扩展访问,因为该模型从根本上依赖于开源客户端技术。这与工作站安全性有关,而不是设计健壮的身份验证/授权机制。我们仍然可以实现某种扩展身份验证机制,但对于知道如何读取扩展源代码的人来说,这将是无用的。

  2. 基本上有两种方法可以设计平台。通过检测API以允许查询身份验证服务。或者使用基于令牌的访问,其中API将请求在其接收的每个请求中存在有效令牌,而不是其发射器。这将意味着使用一个新角色扩展身份验证服务:API票证颁发者,这很少令人感兴趣。当阅读这个命题时,我们似乎通过将会话令牌转发到API来合并两个世界这是错误的首先,这不是设计基于cookie的会话令牌的原因。其次,它迫使API实现某种与用户身份验证服务的会话管理系统的实时同步链接->耦合,我们可以轻松避免这种耦合。

  3. 我认为主要目标是保护API免受未经授权用户的攻击,我们不会试图解决依赖本地系统访问的威胁。

  4. 现在,考虑到我们不会在API中实现身份验证逻辑,我们必须依赖于用户向身份验证服务进行身份验证的模型,从而使任何底层扩展都能够请求访问令牌。

  5. 这将原始场景修改如下:

    • 用户使用浏览器登录网站
    • 网站会发布一个包含会话密钥的cookie
    • 扩展现在可以向身份验证服务发送票证请求。请求将包括会话令牌(默认浏览器行为),因此将进行身份验证
    • 一旦扩展接收到票证,扩展就将其转发给API并请求会话令牌
    • API通过询问会话管理器来验证票证。如果会话管理器说"是的,我做了这个票证,它仍然有效",API将生成一个会话令牌并将其返回给扩展。此令牌将插入所有后续请求中,而不是票证中。这将避免会话管理器上出现任何不必要的工作负载
    • 令牌(不要将其与票证混淆)的使用寿命可能很短,例如几分钟->如果它过期,扩展将返回到身份验证服务并请求新票证(返回到上面的步骤3)
  6. 上述解决方案基本上取决于票证和令牌的安全性。他们的设计必须至少实现针对以下5种剩余威胁的对策:i)试图猜测票证/令牌(足够安全的随机生成),ii,v)尝试在没有有效令牌/票据的情况下访问API(在API接收到的每个请求中验证令牌)。

  7. 这种方法的另一个优点是,我们可以通过发出特定于扩展的令牌来优化资源分配,这反过来将触发API上的特定逻辑(减少API访问、缩短使用寿命、请求节流等)

希望能有所帮助。

所以你在example.com上有一个网站,它需要访问api.com。你的扩展假设用户登录到example.com,提取会话cookie并将其传递到api.com以获得Oauth令牌。听起来很合理,但有一些更简单的方法不必编写浏览器插件。

在您的情况下,api.com将与example.com通信以验证会话cookie。这两个系统之间有很强的依赖性。OAuth通常用于example.com和api.com互不信任的情况。

因为这两个系统已经相互信任,所以可以做各种事情来简化体系结构:

  1. 你可以在example.com/api/*上创建一个代理,验证会话,然后盲目地转发到api.com/*。就浏览器而言,没有跨域请求,所以一切都很好
  2. 您可以跨域使用联合登录。这比代理方法更复杂,但您可以很容易地为您的平台找到现有的实现