Netlify Identity保护Ably应用程序免受黑客攻击

135 阅读11分钟

如何确保你的Aply应用程序被黑掉

登录Ably,复制你的应用程序的API密钥并将其粘贴到你的代码中,像这样:

const ably = new Ably.Realtime('aBCdeFg.ABcDEfG:abc123...789xyz');

然后将这行代码提交到git仓库,部署到生产中,回溯机将把它冻结在琥珀中。就是这么简单。它将在那里等待,直到一个急切的小霍比特人在黑暗中找到它。你宝贵的API密钥将落入坏人之手。

幸运的是,在Ably,我们监控认证密钥的泄漏,并与客户联系。

基本认证就像一个猫瓣 -- 任何老猫都可以进入你的房子。代币认证就像一个带芯片的读卡器:只有符合芯片编程的VIP客人名单的酷猫才能通过挡板测试并进入。

使用Netlify功能来保护你的应用程序

这篇文章告诉你如何用很少的精力来设置令牌认证。我们将使用Netlify函数做一个端点,让你做到这一点。

const authUrl = ".netlify/functions/ably-jwt?id={user-id}";
const ably = new Ably.Realtime({ authUrl });

这看起来很好,没有API密钥的地方,但有什么可以阻止互联网上的人拿着认证URL在其他地方使用呢?没有什么可以阻止他们。令牌认证将隐藏API密钥,但仅此还不够。

我们还需要一种方法,只向有效用户发放令牌。这就是Netfliy的用武之地,我们可以将他们的身份认证服务与无服务器功能结合起来使用,而且这也包含在免费层计划中(本文撰写时)。

TL;DR

如果你只想了解建立自己的Netlify应用的内涵,而不需要所有的解释,那么请到ably-labs的Github repo去,那里的README是本文的浓缩版。

提醒:你需要这些平台的账户。AblyGithubNetlify

如果你只对执行步骤感兴趣,那么请跳转到 好了,好了,够了......让我们发货吧!

什么是 "Ably "JWT令牌?

JSON网络令牌(JWT)是一个开放的标准(RFC 7519),它定义了一种紧凑和独立的方式,以JSON对象的形式在各方之间安全地传输信息。
(摘自jwt.io/introductio…)

通常情况下,一个编码的JWT令牌看起来像这样。

Netlify Identity protects Ably apps from hacks

在这里试试jwt.io/--由auth0的人制作的

令牌认证是安全的,原因有二。Ably JWT令牌是数字签名的,而且它们会定期过期。此外,你可以监控谁在使用你的应用程序,只要令牌刷新。当JWT过期时,用户会通过你的验证服务器被引导,你可以选择是否重新签发他们的令牌。

严格来说,Ably JWT不是Ably的结构,而是为了与Ably兼容而构建的JWT。如果你想详细了解它是如何工作的,我们的最佳实践指南将涵盖一切。

Netlify无服务器功能和身份工作流程

在我们的例子中,一个新的用户需要注册并确认他们的电子邮件地址,以便在他们登录之前激活自己。在登录时,我们用Netlify身份验证他们,并检查他们是否被标记为禁止。不良行为者不会被授予JWT令牌。有效的用户会被发放一个令牌,以便与Ably进行认证,认证URL中包含他们唯一的ID。

Netlify Identity protects Ably apps from hacks

(1) 用户登录 (2) 检查用户身份 - 有效用户获得JWT令牌并继续使用应用程序,无效用户被拒绝 (3) 使用令牌,等待它过期,然后重复。

Netlify身份允许我们通过编辑与用户账户相关的元数据来管理用户。通过Netlify仪表盘给他们分配一个角色,就可以标记出一个不良行为者。

作为无服务器函数的JWT令牌

我们的JWT端点的关键部分是这个代码片段。无论你使用的是什么平台,这部分都是一样的。这就是Ably JWT令牌的结构,它与标准的JWT不同,因为我们需要在数据有效载荷中增加密钥/值:

function generateAblyJWT(props) {
  const { apiKey, clientId, capability, ttlSeconds } = props;

  const [appId, keyId, keySecret] = apiKey.split(/[\.\:]/g);
  const keyName = `${appId}.${keyId}`;

  const typ = "JWT"; // type
  const alg = "HS256"; // algorithm sha256
  const kid = keyName; // appId + keyId

  const currentTime = Math.floor(Date.now() / 1000);
  const iat = currentTime; // initiated at (seconds)
  const exp = currentTime + ttlSeconds; // expire after

  const header = { typ, alg, kid };
  const claims = {
    iat,
    exp,
    "x-ably-capability": capability,
    "x-ably-clientId": clientId,
  };

  const base64Header = encryptObject(header);
  const base64Claims = encryptObject(claims);

  const token = `${base64Header}.${base64Claims}`;
  const signature = b64(SHA256(token, keySecret));

  const jwt = `${token}.${signature}`;

  console.log({ header, claims, signature, jwt });
  return jwt;
}

generateAblyJWT 的功能几乎与你在阅读Ably的认证方法文档时发现的相同。我们选择Netlify作为本文的平台,但同样的想法也可以应用于任何暴露无服务器端点的平台。例如,你可以用一个 Cloudflare 函数Runkit端点或Herokudyno...等等。从本质上讲,你可以使用任何 "唤醒 "的服务,运行代码,并在响应的有效载荷交付后进入休眠状态。

使用Netlify身份认证来验证我们的用户

接下来的这个函数负责两件事。首先,它是端点本身,所以我们的认证URL将执行这个函数。其次,它与Netlify身份认证连接,以验证调用该终端的用户是值得信赖的。

我想提请你注意下面突出显示的那行代码(ɔ)。这是Netlify的端点,我们将用它来获取具有该ID的用户。id的值来自querystring,我们用它来 "烘焙 "我们的Ably JWT,作为`clientId`属性。这意味着Ably和Netlify在各自的审计日志中都会有相同的ID

Netlify Identity的好处是,一旦你激活它,端点处理程序的第二个参数就有一个填充的用户上下文对象,这就是我们连接前端客户端和底层后台的方式:

const axios = require("axios");
const generateAblyJWT = require("./generate-ably-jwt.js");

exports.handler = async function (event, context) {
  const { queryStringParameters } = event;
  const { id } = queryStringParameters || {};
  const { clientContext } = context || {};

  console.log({ queryStringParameters });
  console.log({ clientContext });

  const { identity } = context.clientContext || {};
  const { token, url } = identity || {};

  const userUrl = `${url}/admin/users/${id}`;  👈👈👈
  const Authorization = `Bearer ${token}`;

  console.log({ identity, id, token, url, userUrl, Authorization });

  let response;

  /*

    We Use client context and querystring ID value
    to check the user exists by retrieving their
    User Identity object.

    Then we inspect that accounts metadata for role flags,
    which are added via the Identity dashboard,
    if it contains "Banned" we do not reissue the token

  */

  await axios
    .get(userUrl, { headers: { Authorization } })
    .then(({ data }) => {
      console.log("Success! User identity", data);

      const banned = /^banned/i;
      const { roles = [] } = data.app_metadata;
      const reject = roles.some((item) => banned.test(item));

      // delegate the error message to the catch clause.
      if (reject) throw new Error(`User with id [${id}] has been banned`);

      const settings = {
        clientId: id,
        apiKey: process.env.ABLY_APIKEY,
        capability: process.env.ABLY_CAPABILITY,
        ttlSeconds: Number(process.env.ABLY_TTLSECONDS),
      };

      response = {
        statusCode: 200,
        body: generateAblyJWT(settings),
        headers: { "Content-Type": "application/jwt" },
      };
    })
    .catch((error) => {
      console.log("Failed to get user!");
      response = {
        statusCode: 500,
        body: `Internal Error: ${error}`,
        headers: { "Content-Type": "text/plain" },
      };
    });

  console.log("response payload", response);
  return response;
};

输出的处理函数是Netlify的 "hello world "教程的修改版。唯一的主要变化是:它与Identity集成,并且返回(又称响应)有头信息Content-Type: application/jwt ,示例主体原来是一个(JSON对象),现在是一个String。

除了这两个主要功能外,还有额外的模板代码,加载一个加密库。 CryptoJS.哦,要确保过期时间被铸成Number()。如果有任何不正确的地方,Ably实时网络将提供一个诊断性错误信息。

前端设置的总结

Netlify Identity protects Ably apps from hacks

模态弹出窗口,进行注册、登录和密码提醒等操作,由一个div(ɔ)和一个来自Netlify的JavaScript控制。身份小部件

<main>
  <div>
    <a href="https://ably.com/" target="_blank">
      <img src="images/motif-red.svg?netlify-identity-jwt" 
           alt="Ably logo"/></a>
    <h1>
      Ably JWT Authentication <br />
      using Netlify Identity and <br />
      serverless functions
    </h1>

    <div data-netlify-identity-menu></div>  👈👈👈

    <span class="container">
      <button onclick="go(this)">authenticate</button>
      <strong class="message"></strong>
    </span>
  </div>
</main>

AUTHENTICATE按钮执行了这个功能......

function go(el) {
  /*
    get the user id from localstorage.
    This is only created once the user logs in
  */
  const user = localStorage.getItem("gotrue.user") || null;

  if (!user) {
    showMessage("Can't access user ID, please log in first.");
    return null;
  }

  /*
     Add the User's identity ID to the authURL
     to ensure the Ably token refresh has that credential
     we are binding the authURL and the user
  */

  const { id } = JSON.parse(user);
  const { origin } = window.location;
  const authUrl = `${origin}/.netlify/functions/ably-jwt?id=${id}`;

  console.log({ authUrl });

  window.ably = new Ably.Realtime({ authUrl });
  window.ably.connection.on(handleConnection(el));
}

URL再次被突出显示。这个值加上用户的ID被用来连接客户端和Ably的实时性。这时,根据我们环境变量中设置的TTL值,一个倒计时开始。当它过期时,URL(现在存储在Ably realtime中)将被用来请求更新令牌。

好了,好了,够了......我们开始发货吧

准备工作

我们需要做的第一件事是准备各自的平台。在这里,我们登录我们的Ably账户并创建一个新的应用程序,我们稍后将需要API密钥。然后,我们把Aply Labs的版本库分叉到你自己的版本库。我们需要这样做,以便Netlify可以在下一步导入我们的项目。

Netlify Identity protects Ably apps from hacks

部署和设置Netlify服务

现在我们可以从Github导入源代码到Netlify,并部署应用程序。当应用程序启动和运行时,我们会遇到一些错误,我们需要纠正。当我们修正这些错误时,你会了解到更多关于Netlify的工作原理。

Netlify Identity protects Ably apps from hacks

为了控制谁被允许使用我们的应用程序,我们需要一个用户注册过程。Netlify提供了一个非常好的小工具来创建用户,登录他们,甚至发送密码提醒。此外,你还可以使用SSO登录服务,如谷歌、Github等。对于这个项目,我选择了基本的电子邮件注册。在你开始注册之前,你需要在仪表盘中激活Netlify身份服务。

一个用户只有在确认了他们的电子邮件地址后才被认为是有效的。如果你想的话,这个完整性检查可以被绕过。一旦用户完成了确认,您将看到该身份出现在仪表板上,您将能够编辑他们的元数据。

因此,我们注册了一个用户,并用他们的凭证登录,但当我们试图连接到Ably实时时,仍然看到一个错误。这是因为我们还没有添加API密钥。

添加环境变量

我们通过添加环境变量来完成这个过程,重新部署应用程序并测试用户账户的有效性。在这个阶段,你应该最终看到认证按钮变成绿色,这表明该用户被授予了JWT令牌,并成功连接到Ably realtime网络。

Netlify Identity protects Ably apps from hacks

钥匙类型描述
ABLY_APIKEY字符串您的Ably应用程序的API密钥
ably_capability字符串或空权限的JSON字符串
,比如说

{"channel-name":["subscribe"]}
| | ably_ttlseconds | 数值 | 刷新率,以秒为单位,例如:3600 (60秒) |

能力与Ably APP的权限有关。

你可能想把操作限制为只订阅。要细化权限,请参考我们的文档:能力解释。在这个例子中{"channel-name": ["subscribe", "publish"]} ,意味着状态通道有发布订阅的权限。

这个演示应用程序没有使用通道,它的目的是测试认证,然而Ably JWT在生成时需要分配这个值。

用户节制

在最后一节,我们将假装我们的主要用户不再受欢迎,在身份部分编辑他们的元数据,并在角色属性中添加 "禁止"。从那时起,我们的应用程序将拒绝向该特定用户重新发出JWT。

Netlify Identity protects Ably apps from hacks

总结

现在你对Ably的内部工作有了更多的了解,我们的团队积极检查API密钥的泄露。外部标志确实会出现,如果有什么东西看起来很奇怪,或者有潜在的危险,我们会主动联系。我们非常重视安全问题,对代币认证的推荐也是不遗余力。

在这篇文章中,我们介绍了如何使用Netlify的免费层级产品建立一个认证服务器,如何与身份认证服务整合,并说明了认证端点是多么的简单明了。安全性和节制控制真的值得一试。

有了Netlify,整个开发者的体验都很顺利,从入职,到链接Github,导入项目和部署到生产。我唯一的慢动作是把仪表盘的设置(UI值)移到netlify.toml中,以确保任何使用这个 repo的人都能得到同样简单的体验。

TOML引导了我的默认值,它确定了功能的位置和网站文件的服务来源。还有一个小插曲;弄清楚如何为响应头做应用/jwt内容类型。最初我使用纯文本(这就是JWT字符串),但在这种情况下,头信息必须确定为 "JWT "类型。

最后,说说这个演示的开发过程。大部分工作是通过Netlify CLI完成的,它是一个极好的开发工具。它完美地模拟了无服务器环境,但使用Identify需要你在线完成开发。我遇到了一个关于提供给端点处理程序的上下文对象的错误,它总是缺少用户上下文。这一点在Netlify的Identity文档中得到了强调,他们明确指出Identity需要HTTPS,但是我忽略了这一点,在一天的时间里,我的脸上都是鸡蛋。(脸红)阅读文档!


清理和拆解

当你完成了这个演示,请确保安全地删除它,无论是通过撤销密钥还是删除应用程序。

在这最后一个GIF中,我们将介绍三件事:撤销API密钥(在你被入侵的情况下),这将保留应用程序,但会销毁密钥。完全删除应用程序,这将永久销毁与该应用程序相关的一切,以及最后如何删除Netlify实例。

Netlify Identity protects Ably apps from hacks

Ably简而言之

Ably提供API来实现你的应用程序中的实时功能的pub/sub消息传递。你还可以得到一个全球分布的可扩展的基础设施,以及一套服务的开箱即用。

这些功能包括存在感--显示各种参与者的在线/离线状态,在间歇性网络问题的情况下自动重新连接和恢复消息消息订购和保证交付,以及与第三方API集成的简单方法。

通道的概念允许你对数据进行分类,并决定哪些组件可以访问哪些通道。你还可以为这些通道上的各种参与者指定功能,如只发布、只订阅、消息历史等。

了解更多关于Ably平台的信息


限制你的暴露的工具

Git Guardian让秘密远离你的源代码。它扫描你的源代码,实时检测API密钥、密码、证书、加密密钥和其他敏感数据

GitHub行动- 非常适用于环境保护规则和环境秘密(测试版)

GitHub 秘密扫描合作伙伴- 列出了支持的秘密以及与 GitHub 合作的合作伙伴,以防止意外提交的秘密被欺诈性使用。

全局.gitignore- 项目的.gitignore文件是众所周知的,不太知名的更重要的.gitignore 文件可以在你的机器上设置,并应用于你跟踪的每个git仓库。git config \--global core.excludesfile ~/.gitignore_globalJSON网络令牌(JWT)和测试工具。