设计无密码登录指南

无密码身份验证正成为开发人员越来越受欢迎的选择。甚至像 Slack、Notion 和 PayPal 这样著名的产品都在过渡到 SMS、电子邮件或社交登录来进行身份验证。

它越来越多地被采用的一个原因是它天生就不太容易受到网络攻击。由于密码哈希都没有存储在数据库中(它们基于所使用的散列和加盐方案具有不同程度的安全性),因此没有暴露的攻击面和用户凭据供恶意用户攻击。

无密码有一些缺点。用户体验在用户导航离开页面时变得更加复杂。此外,一些实施会增加成本(例如,使用物理令牌),当然 - 没有系统可以完全可靠地对抗恶意攻击。

如果您正在寻找关于 Passworld 可能的不同选项的好入门文章,可以参考本指南

在这篇文章中,我们将重点介绍软件的实现--在我们的示例中,它将使用电子邮件的登录方式。

以下是启动和运行无密码身份验证所需要做的事情:

1、创建生成 token 的函数

为了简单起见,我们将使用 JWT 作为我们的令牌。

伪代码:

function generateToken(){
    /* 用你喜欢的JWT库创建令牌。我们建议你添加一个超时时间,以获得最佳安全实践 */
    const token = jwt.sign(
      { user_id: user._id, email },
      process.env.TOKEN_KEY,
      {
        expiresIn: "10m",
      }
    );

    return token;
}

2、创建一个接口来发送你的验证链接

创建验证链接本身非常简单——Token 本身将位于我们生成的链接的查询参数中。我们将调用我们刚刚编写的方法来生成 token。

app.get('/send-link', (req, res) => {
     // 生成 token
    const token = generateToken();
     // 如果您想使重复请求的旧 token 无效,请将 token 存储在您的数据库中
    //db_saveNewVerificationToken(req.query.email, token);
     // 使用生成的 JWT 创建验证链接
    const magicLink = 'https://example.com/auth/verify-login?token=' + token;
     const mailConfigurations = {
        from: '[email protected]',
        to: req.query.email,
        subject: 'Log into Example',
        text: magicLink     };
         // 将电子邮件发送到用户的收件箱
    sendMail(mailConfigurations);
     res.status(200);
    res.json({ emailSuccess: true });
    res.end();

});

3、创建用于验证令牌的接口

app.get('/verify-token', (req, res) => {

    // 如果只验证最新的令牌,请通过用户的电子邮件查找保存的令牌
    //const signature = db_lookupVerificationToken(req.query.email);

    // 使用密钥,通过将令牌与从头和有效负载生成的测试签名进行比较来验证令牌
    const decode = jwt.verify(token);

    if(decode === true){ // 返回状态响应
        res.status(200);
        res.json({
            // 在此处创建新会话
            data: decode
        });
        res.end();
    }
    else {
        res.status(401);
        res.json({
                    login: false,
                    data: decode
                });
        res.end();
    }
  //  返回带有登录数据的响应

});

4、整合上面步骤

现在您已经准备好了所有的部分,您可以将前端连接到两个后端 API 方法以在用户登录时对其进行身份验证。

1、当用户提交带有电子邮件的表单以登录时,调用发送链接接口将向他们的收件箱发送一封带有验证链接的电子邮件 - 提示用户检查他们的电子邮件收件箱。

2、一旦他们打开电子邮件,点击链接应该会打开一个专门的验证页面或他们的目标页面。

3、使用从链接中获取的令牌,前端应调用验证令牌接口进行验证。

4、在收到表明验证成功的响应后,将创建一个会话并授予用户访问您的应用程序的权限 - 否则应建立一个流程以将用户重新重定向到登录页面以重试。

注意

需要记住的一些细节:

1、防止用户打开验证链接的浏览器与他们进行登录的浏览器不是一个或不同的设备上(如想在pc上登录,但邮箱客户端是在手机上,若直接点击就在手机上登陆了)。最简单的解决方案是在使用验证链接之前通过在客户端上单击按钮来提示用户。

2、为解决用户登录和点击验证使用两个不同的浏览器的问题。我们可能设计成支持在登录界面直接登录(如js轮训验证用户有没有点击链接,若点击了则自动登录),但这存在安全漏洞,攻击者可能会向用户发送链接。当用户点击链接时,攻击者也会登录。

出于这个原因,最好登录只在点击验证链接的新的浏览器会话中,但这可能会导致糟糕的用户体验,即用户登录到与他们想要的设备不同的设备或不同的浏览器。折衷方案是在用户点击验证链接时让用户选择在哪个设备中登录,这样当用户返回到他们预期的浏览器会话时,才有最好的用户体验。

实际开发时 只在点击验证链接的新的浏览器会话中让用户登录就行了,安全更重要,可以牺牲点用户体验

3、在此示例中,我们选择 JWT 作为令牌。或者,您可以使用存储在数据库中的不透明令牌 - 虽然实现更复杂,但它也更安全,因为没有固定的的密钥。

4、要限制尝试次数(使用锁定功能)以防止暴力攻击。

5、在安全发送链接到电子邮件时使用 SSL/TLS 加密,以确保电子邮件的有效负载(包括验证链接)无法被篡改。

6、为您的令牌选择尽可能短的超时时间,以限制攻击者使用令牌的机会窗口。如果令牌永不过期,则攻击者可以随时使用该令牌。

结论

虽然我们鼓励您将此作为实施无密码身份验证的指南,但安全性是实施任何形式的身份验证的关键组成部分。

如需更多详细信息并在几分钟内开始使用无密码身份验证,我们邀请您试用 SuperTokens Passwordless方案

引用链接

[1] 本指南: https://supertokens.com/blog/passwordless-for-product-managers
[2] SuperTokens Passwordless: https://supertokens.com/docs/passwordless/introduction