无password身份验证:安全、简单且部署高速

Passwordless authentication: Secure, simple, and fast to deploy

【编者按】本文做者为 Florian HeinemannRobert Nymanjavascript

Florian 来自 MIT 系统设计与管理学院,专一于复杂的社交技术系统。此前曾在企业软件领域的多家初创公司工做,以后加入 Airbus,担任知识与创新管理经理一职。Robert 是 Mozilla Hacks 技术传道师及编辑。曾就 HTML5,JavaScript 以及 Open Web 发表过屡次谈话与博文。Robert 坚决地看好 HTML5 与 Open Web。自1995年就開始在 Front End 开发部门研究 Web 技术。php

本文系 OneAPM project师编译呈现,下面为正文。html

Passwordless(无密码)是用于 Node.js 程序的一种身份验证中间件。能提升用户安全水平。同一时候具有部署简单、高速的特色。java

过去几个月。对热衷于 Web 安全与保密性的人来讲。着实激动人心:出现了不少了不得的文章讨论。还有不少事件,都在提升人们的安全意识。node

然而,大多数站点仍在使用最先期的 web 身份验证方式:username和password。git

虽然username密码这样的身份验证方式的确占领了一席之地。但假设觉得这是所有项目的终极选择,咱们便应该更加慎重了。咱们知道,大多数人在訪问站点时都使用同一套密码github

对于那些缺乏安全专家支持的 web 项目,假设用户在该站点的密码遭到泄露,那就可能伤及他的 Amazon 帐户。咱们真的要让用户承担这样的风险么?此外。这样的经典的身份验证机制至少存在两种攻击角度:登陆页与密码找回页。而且。后者的实现每每在匆忙中进行。于是风险更高。web

近期。咱们看到了不少不错的点子mongodb

笔者尤为对一个直观而且低技术含量的解决方法感兴趣:一次性密码。这样的方法部署高速。攻击面小,而且不需要 QR codes 或 JavaScript。无论什么时候,用户想要登陆或使以前的会话失效,都可以经过电子邮件或短信息收到一个短期有效的一次性连接与令牌(token)。假设你想试一试,可以下载passwordless.net中的演示代码。数据库

不幸的是。因为技术栈的区别。基本上不存在现成的解决方式。

所以,Passwordless 针对 Node.js 作出了一些修改。

从 Node.js 与 Express 入手

Passwordless 入门很easy。两个小时之内。你就能学会部署全面且安全的身份验证解决方式:

$ npm install passwordless --save

获取主要的框架。你还要安装某个现成的存储接口,比方 MongoStore,用于安全地存储令牌。

$ npm install passwordless-mongostore --save

在传送令牌给用户时。电子邮件通常是最好的选择(只是。短消息也可以)。

你可以随意选择邮件框架,比方:

$ npm install emailjs --save

基本设置

首先,请求上文用到的所有模块,将它们放在用于初始化 Express 的同一个文件里:

var passwordless = require('passwordless');
var MongoStore = require('passwordless-mongostore');
var email   = require("emailjs");

假设你选用 emailjs 进行令牌传递。此时应该与邮箱帐户进行链接(比方:Gmail 帐户):

var smtpServer  = email.server.connect({
   user:    yourEmail,
   password: yourPwd,
   host:    yourSmtp,
   ssl:     true
});

最后的一个预备步骤是告知 Passwordless 你选择了哪种存储接口。并将之初始化:

// Your MongoDB TokenStore
var pathToMongoDb = 'mongodb://localhost/passwordless-simple-mail';
passwordless.init(new MongoStore(pathToMongoDb));

传递令牌

函数 passwordless.addDelivery(deliver) 会加入新的传送机制。每次需要传送令牌时。就会调用deliver。默认状况下,你选择的机制应该依照下面格式为用户提供连接:

http://www.example.com/token={TOKEN}&uid={UID}

deliver 在调用时。需要所有的细节信息。所以,令牌的传递(在本例中,使用的是 emailjs)例如如下所看到的。至关简单:

passwordless.addDelivery( function(tokenToSend, uidToSend, recipient, callback) { var host = 'localhost:3000'; smtpServer.send({ text: 'Hello!nAccess your account here: http://' + host + '?

token=' + tokenToSend + '&uid=' + encodeURIComponent(uidToSend), from: yourEmail, to: recipient, subject: 'Token for ' + host }, function(err, message) { if(err) { console.log(err); } callback(err); }); });

初始化 Express 中间件

app.use(passwordless.sessionSupport());
app.use(passwordless.acceptToken({ successRedirect: '/'}));

函数 sessionSupport() 会使登陆状态获得保持,所以。用户在浏览站点时才干一直处于登陆状态。请确保你已经提早准备好了会话中间件(比方express-session)。

函数 acceptToken() 会截获不论什么外来的令牌,验证用户身份,再将他们重定向至正确的页面。

虽然 successRedirect 选项不是严格要求的。但笔者强烈建议你使用此选项,从而避免合法令牌经过站点向外的 HTTP 连接的 header 来源泄露出去。

路径选择与身份验证

下文默认你已经经过 var router = express.Router(); 配置好路径选择器。

此外,express 文档中也有对应的说明。

你至少需要两个 URLs,从而:
- 展现用于获取用户邮箱地址的页面
- 获取表格细节(经过 POST 方法)

/* GET: login screen */
router.get('/login', function(req, res) {
   res.render('login');
});</p>

/* POST: login details */
router.post('/sendtoken',
    function(req, res, next) {
        // TODO: Input validation
    },
    // Turn the email address into a user ID
    passwordless.requestToken(
        function(user, delivery, callback) {
            // E.g. if you have a User model:
            User.findUser(email, function(error, user) {
                if(error) {
                    callback(error.toString());
                } else if(user) {
                    // return the user ID to Passwordless
                    callback(null, user.id);
                } else {
                    // If the user couldn’t be found: Create it!
                    // You can also implement a dedicated route
                    // to e.g. capture more user details
                    User.createUser(email, '', '',
                        function(error, user) {
                            if(error) {
                                callback(error.toString());
                            } else {
                                callback(null, user.id);
                            }
                    })
                }
        })
    }),
    function(req, res) {
        // Success! Tell your users that their token is on its way
        res.render('sent');
});

此处有何猫腻?passwordless.requestToken(getUserId) 的任务有二:第1、确保邮箱地址真实存在。第2、将该地址转变为独一无二的用户 ID,经过邮件一并发送,并在以后用于验证用户身份。一般,你都有一套存储用户细节信息的模型,你可以依照上例的说明。进行简单的设置。

在一些状况下(好比。由两个用户编辑过的博客)。你可以跳过用户模型,将他们有效的邮箱地址与其各自的 ID 相联系:

var users = [
    { id: 1, email: 'marc@example.com' },
    { id: 2, email: 'alice@example.com' }
];

/* POST: login details */
router.post('/sendtoken',
    passwordless.requestToken(
        function(user, delivery, callback) {
            for (var i = users.length - 1; i >= 0; i--) {
                if(users[i].email === user.toLowerCase()) {
                    return callback(null, users[i].id);
                }
            }
            callback(null, null);
        }),
        // Same as above…

HTML 页面

本例仅仅需要一个简单的 HTML 页面,用以获取用户的邮箱地址。

默认状况下,Passwordless 会查找 user 输入栏中的内容:

<html>
    <body>
        <h1>Login</h1>
        <form action="/sendtoken" method="POST">
            Email:
            <br /><input name="user" type="text">
            <br /><input type="submit" value="Login">
        </form>
    </body>
</html>

保护网页

Passwordless 提供了能确保仅仅有验证用户才干看到指定页面的中间件:

/* Protect a single page */
router.get('/restricted', passwordless.restricted(),
 function(req, res) {
  // render the secret page
});

/* Protect a path with all its children */
router.use('/admin', passwordless.restricted());

谁处于登陆状态?

默认状况下,Passwordless 赞成经过请求对象 req.user 获取用户 ID。想要展现或重用此 ID,或从数据库中获得不少其它细节信息,你可以这么实现:

router.get('/admin', passwordless.restricted(),
    function(req, res) {
        res.render('admin', { user: req.user });
});

或者。更通常化地,你可以加入还有一个中间件,从模型中抽取出某个用户的所有记录,再将其分配给站点中的随意路径:

app.use(function(req, res, next) {
    if(req.user) {
        User.findById(req.user, function(error, user) {
            res.locals.user = user;
            next();
        });
    } else {
        next();
    }
})

到此为止啦!

以上就是安全地验证用户身份的简单方法。

若想了解不少其它细节,你可以查看深刻剖析,从而了解所有的可选项。以及将上述知识整合为一套可行的解决方式的案例。

点评

如前所看到的。所有的身份验证系统都有其优缺点。你应该依照本身的需求进行合理的选择。基于令牌的验证方式,与绝大多数解决方法(包含经典的username/密码方法)同样。都存在一个风险:假设用户的邮箱帐户被盗用。而且/或者 SMTP 服务器与用户的链接被入侵,用户在站点的帐户就会随之遭到盗用。默认状况下。有两种办法可以减弱该风险(但不是全然避免):短期有效的令牌以及令牌在使用后本身主动失效。

对大多数站点而言,基于令牌的身份验证方法表明着走向安全的进步:用户无需再想新的密码(这些密码每每很easy)。也不存在用户重用密码的风险。

对于身为开发人员的咱们。Passwordless 提供了惟一一种(且至关简单的)身份验证解决方式,该方案易于理解。所以easy保护。此外,咱们也无需再处理用户的密码了。

还有一个值得讨论的点是可用性。咱们应该同一时候考虑两种状况:用户在站点的首次使用以及兴许的登陆。对首次用户而言。基于令牌的身份验证很直观:与经典的登陆机制同样,他们仍是不得不验证邮箱地址。

但是,在最佳案例中,除此以外就不需要其它细节信息了。不需要再煞费苦心地想一个符合所有限制条件的密码,也不需要刻意记住密码。假设用户再次登陆,其体验与特定的用户案例有关。大多数站点的会话有效期都很长,于是不需要再次登陆。

或者,用户訪问该站点的频率事实上很低,以至于他们想不起来本身是否已经拥有帐户,或者忘记了帐户密码。在这样的状况下。Passwordless 在可用性方面的优点至关明显。相同地,这需要经历很少的几个步骤,解释起来也很easy。然而,那些用户訪问频繁的站点。以及/或那些要求用户一周内手动登陆几回的站点(比方 Amazon),经典的身份验证(或更保险地:双重验证)也许更加适合。因为用户极可能会真的记住他们的密码,而且意识到好密码的重要性。

虽然 Passwordless 眼下比較稳定,笔者仍是很期待你能在 GitHub 留下评论或一些贡献,或者在 Twitter:@thesumofall 上提问笔者。

原文地址:https://hacks.mozilla.org/2014/10/passwordless-authentication-secure-simple-and-fast-to-deploy/

OneAPM 助您轻松锁定 Node.js 应用性能瓶颈,经过强大的 Trace 记录逐层分析,直至锁定行级问题代码。以用户角度展现系统响应速度。以地域和浏览器维度统计用户使用状况。

想阅读不少其它技术文章。请訪问 OneAPM 官方博客

本文转自 OneAPM 官方博客

相关文章
相关标签/搜索