我搜索了大量关于 Node.js/Express.js 认证的教程。全部这些都是不完整的,甚至以某种方式形成安全错误,可能会伤害新用户。当其余教程再也不帮助你时,你或许能够看看这篇文章,这篇文章探讨了如何避免一些常见的身份验证陷阱。同时我也一直在 Node/Express 中寻找强大的、一体化的解决方案,来与 Rails 的 devise 竞争。php
更新 (8.7): 在他们的教程中,RisingStack 已经声明,不要再以明文存储密码,在示例代码和教程中选择使用了 bcrypt。html
更新 (8.8): 编辑标题 关于 Node.js 的认证方面的教程(极可能)是有误的,这篇文章已经对这些教程中的一些错误点进行了改正。前端
在业余时间,我一直在挖掘各类 Node.js 教程,彷佛每一个 Node.js 开发人员都有一个博客用来发布本身的教程,讲述如何以正确的方式作事,或者更准确地说,他们作事的方式。数以千计的前端开发人员被投入到服务器端的 JS 漩涡中,试图经过拷贝式的操做或免费使用的 npm install 将这些教程中的可操做的知识拼凑在一块儿,从而在外包经理或广告代理商给出的期限内完成开发。node
Node.js 开发中一个更有问题的事情就是身份验证的程序很大程度上是开发人员在摸索中完成开发的。事实上 Express.js 世界中的认证解决方案是 Passport,它提供了许多用于身份验证的策略。若是你想要一个相似于 Plataformatec 的 devise 的 Ruby on Rails 的强大的解决方案,你可能会对 Auth0 感兴趣,它是一个使认证成为服务的开创项目。nginx
与 Devise 相比,Passport 只是身份验证中间件,不会处理任何其余身份验证:这意味着 Node.js 开发人员可能会定制本身的 API 令牌机制、密码重置令牌机制、用户认证路由、端点、多种模板语言,所以,有不少教程专门为你的 Express.js 应用程序设置 Passport,可是几乎没有彻底正确的教程,没有一个正确地实现出 Web 应用程序所需的完整堆栈。git
请注意: 我不是故意针对这些教程的开发人员,而是使用他们的身份验证所存在的漏洞后会让本身的身份验证系统产生安全问题。若是你是教程做者,请在更新教程后随时与我联系。让 Node/Express 成为开发人员使用的更安全的生态系统。github
让咱们从凭证存储开始。存储和调用凭证对于身份管理来讲是很是标准的,而传统的方法是在你本身的数据库或应用程序中进行存储或者调用。凭证,做为中间件,简单地说就是“这个用户能够经过”或“这个用户不能够经过”,须要 passport-local 模块来处理在你本身的数据库密码存储,这个模块也是由 Passport.js 做者写的。web
在咱们进入这个教程的兔子洞以前,请记住 OWASP 的密码存储做弊表,它归结为“存储具备独特盐和单向自适应成本函数的高熵密码”。或者先看下 Coda Hale 的 bcrypt meme,即便有一些争论。数据库
做为一个新的 Express.js 和 Passport 用户,我第一个要讲的地方将是 passport-local 自己的示例代码,十分感谢 passport 官方提供了一个能够克隆和扩展的 Express.js 4.0 应用程序示例,从而我能够克隆和扩展。可是,若是我只是拷贝这个例子,我讲不了太多,由于没有数据库支持的例子,它假设我只是使用一些设置好的账户。express
不要紧,对吧?这只是一个内联网应用程序,开发人员说,下周将分配给我另外四个项目。固然,该示例的密码不会以任何方式散列,而且与本示例中的验证逻辑一块儿存储在明文中。在这一点上,甚至没有考虑到凭证存储。
让咱们来 google 另外一个使用 passport-local 的教程。我发现这个来自 RisingStack 的一个叫“Node Hero”系列的快速教程,但从这个教程中我没找到颇有用的帮助。他们也在 GitHub 上提供了一个示例应用程序,
但它与官方的问题相同。(Ed。8/7/17:RisingStack 如今使用 bcrypt 在他们的教程应用。)
接下来,这是第四个结果,来自写于 2015 年的 Google 产出的 express js passport-local 教程。它使用 Mongoose ODM,实际上从个人数据库读取凭据。 这一个教程算是比较完整的,包括集成测试,是的,你可使用另外一个样板。可是,Mongoose ODM 也存储类型为 String 的密码,因此这些密码也存储在明文中,只是这一次在 MongoDB 实例上。(人人都知道 MongoDB 实例一般是很是安全的)
你能够指责我择优挑选教程,若是择优挑选意味着从 Google 搜索结果的第一页进行选择,那么你会是对的。让咱们选择 TutsPlus 上更高排名的 passport-local 教程。这一个更好,由于它使用 brypt 的因子为 10 的密码哈希,并使用 process.nextTick 延迟同步 bcrypt 哈希检查。Google 的最高成绩来自 scotch.io 的教程,也使用 成本因子较低为 8 的 bcrypt。这两个值都很小,可是 8 真的很小。大多数 bcrypt 库如今使用 12。选择 8 做为成本因子是由于管理员账户是十八年前的,这个因子数在那时候就能知足需求了。
除了密码存储以外,这些教程都不会实现密码重置功能,这将做为开发人员的一个挑战,而且它附带着本身的陷阱。
密码存储的一个姐妹安全问题是密码重置,而且没有一个顶级的基础教程解释了如何使用 Passport 来完成此操做。你必须另寻他法。
有一千种方法去搞砸这个问题。我见过的最多见人们从新设置密码错误是:
若是你是第一次接触这些内容,请尝试 OWASP 的密码重置工做表。让咱们回到 Node 中看看它为此提供给咱们的东西。
咱们将转移到 npm 一秒钟,并从新查找密码重置,看看是否已有人作到这一点。有一个已有五年历史的 package(一般意味着它很棒)。在 Node.js 的时间轴上,这个模块就像是侏罗纪时代的,若是我想要鸡蛋里挑骨头,Math.random() 能够在 V8 中预测,所以它不该该用于令牌生成码。此外,它不使用 Passport,因此咱们继续前进。
Stack Overflow 上获取不了太多的帮助,由于一个名叫 Stormpath 的公司的开发人员喜欢在能够想象到的每个跟这个相关的的帖子上都插入他们的 IaaS 启动教程。他们的文档也随处可见,他们也有关于密码重置的博客广告。可是,全部这一切都随着 Stormpath 的停业已经中止了,它们公司于 2017 年 8 月 17 日彻底关闭。
好的,回到谷歌,这里彷佛存在惟一的教程。咱们找到了 Google 搜索 express passport 密码重置的第一个结果。仍是咱们的老朋友 bcrypt。文章中使用了更小的成本因子 5,这远远低于了现代使用的成本因素。
可是,与其余教程相比,这篇教程至关实用,由于它使用 crypto.randomBytes 来生成真正的随机标记,若是不使用它们,则会过时。然而,上述实践中的 #2 和 #4 与这个全面的教程不符,所以密码令牌自己容易受到认证错误,凭据存储的影响。
幸运的是,因为重置到期,这是有限的使用。可是,若是攻击者经过 BSON 注入对数据库中的用户对象进行读取访问,或因为配置错误,能够自由访问 Mongo,这些令牌将很是危险了。攻击者只需为每一个用户发出密码重置,从 DB 读取未加密的令牌,并为用户账户设置本身的密码,而没必要经历使用 GPU 装备对 bcrypt 散列进行的昂贵的字典攻击过程。
API 令牌是凭据。它们与密码或重置令牌同样敏感。大多数开发人员都知道这一点,并尝试将他们的 AWS 密钥、Twitter 秘密等保留在他们胸前,可是这彷佛并无转移到被编写的代码中。
让咱们使用 JSON Web 令牌获取 API 凭据。拥有一个无状态的、可添加黑名单的、可自定义的令牌比十年来使用的旧 API 密钥/私密模式更好。也许咱们的初级 Node.js 开发人员曾经据说过 JWT,或者看到过 passport-jwt,并决定实施 JWT 策略。不管如何,接触 JWT 的人都会或多或少地受到 Node.js 的影响。(尊敬的Thomas Ptacek 会认为 JWT 很差,但恐怕船已经在这里航行。)
咱们在 Google 上搜索 express js jwt,而后找到 Soni Pandey 的教程使用 Node.js 中的 JWT(JSON Web 令牌)进行用户验证,。不幸的是,这教程实际上并不帮助咱们,由于它没使用凭证,可是当咱们在这里时,咱们会很快注意到凭据存储中的错误:
让咱们回到 Google,接着寻找下一个教程。Scotch,在 passport-local 教程中作了一个密码存储的工做,好比只是忽略他们之前告诉你的东西,并将密码存储在明文中。
好吧,咱们会给出一个简短的凭证教程,但这并不能帮助只是拷贝的开发者。由于更有趣的是,这个教程将这个 mongoose User 对象序列化到 JWT 中。
让咱们克隆 Scotch 的这个资源库,按照说明进行运行。能够无视一些来自 Mongoose 的警告,咱们能够输入 http://localhost:8080/setup 来建立用户,而后经过使用 “Nick Cerminara” 和 “password” 的默认凭证调用 /api/authenticate 拿到令牌。这个令牌返回并显示在了 Postman 上。
从 Scotch 教程返回的 JWT 令牌。
请注意,JSON Web 令牌已签名但未加密。这意味着两个时期之间的大斑点是一个 Base64 编码对象。快速解码后,咱们获得一些有趣的东西。
我喜欢在明文的密码中使用令牌。
如今,任何一个包括存储在 Mongoose 模型甚至过时的令牌都有你的密码。鉴于这个来自HTTP,我能够把它从线上找出来。
下一个教程怎么样呢?下一个教程,针对初学者的 Express、Passport 和 JSON Web 令牌(jwt),包含相同的信息泄露漏洞。下篇教程来自 SlatePeak 的一篇作了一样的序列化文章。在这一点上,我放弃了阅读。
如上所述,我没有在任何这些身份验证教程中找到关于速率限制或账户锁定的问题。
没有速率限制,攻击者能够执行在线字典攻击,好比运行 Burp Intruder 等工具,去得到获取访问密码较弱的账户。账户锁定还能够经过在下次登陆时要求用户填写扩展登陆信息来帮助解决此问题。
请记住,速率限制还有助于可用性。跨平台文件加密工具是一个 CPU 密集型功能,没有速率限制功能,使用跨平台文件加密工具会让应用程序拒绝服务,特别是在 CPU 高数运行时。好比用户注册或检查登陆密码的多个请求尽管是轻量级的 HTTP 的请求,可是会花费服务器大量的昂贵时间。
虽然我没有教程能够证实这点,但 Express 有不少速率限制的技术,例如 express-rate-limit,express-limiter 以及 express-brute。我不能评价这些模块的安全性,甚至没有看过它们;不管你的负载平衡用的是什么,一般我推荐在生产中运行逆向代理,并容许由 nginx 限制请求处理速率。
我相信这些有错误的教程开发人员会辩解说,“这只是为了解释基础!没有人会在生产中这样作的!”可是,我再三强调了这是多么错误。当你的教程中的代码被放在这里时,人们就会参考并使用你的代码,毕竟,你比他们有更多的专业知识。
若是你是初学者,请不要信任你的教程。 拷贝教程中的例子可能会让你、你的公司和你的客户在 Node.js 世界中遇到身份验证问题。若是你真的须要强大的生产完善的一体化身份验证库,那么可使用更好的手段,好比使用具备更好的稳定性,并且更加经验证的 Rails/Devise。
Node.js 生态系统虽然容易接近,但对须要匆忙编写部署于生产环境的 Web 应用程序的 JavaScript 开发人员来讲,仍然有不少尖锐的未解决的点。若是你有前端的背景,不知道其余的编程语言,我我的认为,使用 Ruby 是一个不错的选择,毕竟站在巨人的肩膀上比从头开始学习这些类型的东西要容易。
若是你是教程做者,请更新你的教程,特别是样板代码。这些代码将可能被其余人拷贝到生产环境中的 web 应用程序。
若是你是一个 Node.js 的铁杆使用者,但愿你在这篇文章中学到一些关于使用用凭证验证身份的知识。你可能会遇到什么问题。这篇文章中我尚未找到完美的方法来彻底避免以上错误。为你的 Express 应用程序增长凭证验证不该该是你的工做。应该有更好的办法。