本文概述
- 如何为一键式登录流程使用元掩码
- MetaMask浏览器扩展
- 登录流程如何工作
- 为什么登录流程有效
- 让我们一起构建
- 今天就可以投入生产
- 手机的缺点
- 让你的用户使用MetaMask登录
社交媒体登录集成的优点:
- 不再需要繁琐的表格填写。
- 无需记住另一个用户名/密码对。
- 整个过程只需几秒钟而不是几分钟。
- 由于用户的信息是从外部提供商处加载的, 因此, 对于提供商如何使用所有这些个人数据引起了极大的隐私问题。例如, 在撰写本文时, Facebook面临数据隐私问题。
一张价值一千个单词的图片, 这是我们要构建的登录流程的演示:
文章图片
看起来不错?让我们开始吧!
如何为一键式登录流程使用元掩码 基本思想是, 通过使用私钥对数据签名来证明帐户所有权在密码上很容易。如果你设法签署由我们后端生成的精确数据, 则后端将认为你是该公共地址的所有者。因此, 我们可以建立一个基于消息签名的身份验证机制, 以用户的公共地址作为其标识符。
如果不清楚, 那就可以了, 因为我们将逐点进行说明:
- MetaMask浏览器扩展
- 登录流程如何工作
- 为什么登录流程有效
- 让我们一起构建
- 今天就可以投入生产
- 手机的缺点
MetaMask浏览器扩展 如果你已经知道MetaMask是什么, 请随时跳过此部分。
MetaMask是一个浏览器插件, 可以通过MetaMask Chrome扩展或Firefox附加组件获得。它的核心是用作以太坊钱包:安装后, 你将可以访问唯一的以太坊公共地址, 你可以使用该地址开始发送和接收以太坊或令牌。
但是, MetaMask所做的不只是以太坊钱包。作为浏览器扩展程序, 它可以与你正在浏览的当前网页进行交互。为此, 你可以在访问的每个网页中注入一个名为web3.js的JavaScript库。注入后, 可通过该网站的JavaScript代码中的window.web3使用web3对象。要查看该对象的外观, 请在Chrome或Firefox DevTools控制台中键入window.web3(如果已安装MetaMask)。
Web3.js是以太坊区块链的JavaScript接口。有以下功能:
- 获取链的最新块(web3.eth.getBlockNumber)
- 检查MetaMask(web3.eth.coinbase)上的当前活动帐户
- 获取任何帐户的余额(web3.eth.getBalance)
- 发送交易(web3.eth.sendTransaction)
- 使用当前帐户的私钥(web3.personal.sign)签署消息
- …以及更多
与DApp开发相关:锁时钱包:以太坊智能合约简介
web3.js中的大多数函数都是读取函数(获取块, 获取平衡等), 并且web3将立即给出响应。但是, 某些功能(例如web3.eth.sendTransaction和web3.personal.sign)需要当前帐户使用其私钥对某些数据进行签名。这些功能触发MetaMask显示确认屏幕, 以再次检查用户是否知道自己正在签名。
让我们看看如何使用MetaMask。要进行简单测试, 请将以下行粘贴到DevTools控制台中:
web3.personal.sign(web3.fromUtf8("Hello from srcmini!"), web3.eth.coinbase, console.log);
此命令的意思是:使用coinbase帐户(即活期帐户)签署从utf8转换为hex的我的消息, 并作为回叫打印签名。将显示一个MetaMask弹出窗口, 如果你对其进行签名, 则将打印已签名的消息。
文章图片
我们将在登录流程中使用web3.personal.sign。
关于本节的最后说明:MetaMask将web3.js注入当前的浏览器, 但是实际上还有其他独立浏览器也注入web3.js, 例如Mist。但是, 我认为, MetaMask为当今的普通用户提供了最好的UX和最简单的过渡, 使其可以探索dapp。
登录流程如何工作 让我们从如何开始。该方法有望使你确信它是安全的, 因此, 我将简短说明为什么。
如概述中所述, 我们将忘记区块链。我们拥有传统的Web 2.0客户端-服务器RESTful体系结构。我们将做一个假设:所有访问我们的前端网页的用户都安装了MetaMask。以此假设为基础, 我们将展示无密码的密码安全登录流程如何工作。
步骤1:修改用户模型(后端)
首先, 我们的用户模型需要具有两个新的必填字段:publicAddress和nonce。此外, publicAddress必须是唯一的。你可以保留通常的用户名, 电子邮件和密码字段(特别是如果要与电子邮件/密码登录并行地实现MetaMask登录), 但是它们是可选的。
注册过程也将略有不同, 因为如果用户希望使用MetaMask登录名, 那么publicAddress将是注册时的必填字段。请放心, 用户无需手动键入其publicAddress, 因为可以通过web3.eth.coinbase来获取。
步骤2:产生随机数(后端)
对于数据库中的每个用户, 在随机数字段中生成一个随机字符串。例如, 随机数可以是一个大的随机整数。
第3步:用户获取其现值(前端)
在我们的前端JavaScript代码中, 假设存在MetaMask, 我们可以访问window.web3。因此, 我们可以调用web3.eth.coinbase来获取当前MetaMask帐户的公开地址。
当用户单击登录按钮时, 我们向后端触发API调用, 以检索与他们的公共地址关联的随机数。应该使用带有过滤器参数GET / api / users?publicAddress = $ {publicAddress}的路由之类的东西。当然, 由于这是未经身份验证的API调用, 因此后端应配置为仅显示此路由上的公共信息(包括随机数)。
如果上一个请求未返回任何结果, 则表明当前公共地址尚未注册。我们首先需要通过POST / users创建一个新帐户, 并在请求正文中传递publicAddress。另一方面, 如果有结果, 则我们存储其随机数。
步骤4:用户对Nonce(前端)进行签名
前端在上一个API调用的响应中收到随机数后, 它将运行以下代码:
web3.personal.sign(nonce, web3.eth.coinbase, callback);
这将提示MetaMask显示用于签名消息的确认弹出窗口。随机数将显示在此弹出窗口中, 以便用户知道她或他没有对某些恶意数据进行签名。
接受回调函数时, 将使用签名消息(称为签名)作为参数来调用回调函数。然后, 前端对POST / api / authentication进行另一个API调用, 同时传递带有签名和publicAddress的主体。
步骤5:签名验证(后端)
当后端收到POST / api / authentication请求时, 它首先在与请求正文中给定的publicAddress对应的数据库中提取用户。特别是, 它获取相关的随机数。
拥有了随机数, 公共地址和签名后, 后端可以通过密码验证该随机数已由用户正确签名。如果是这种情况, 则表明用户已经证明了对公共地址的所有权, 因此我们可以认为该用户已通过身份验证。然后, 可以将JWT或会话标识符返回到前端。
步骤6:更改Nonce(后端)
为了防止用户使用相同的签名再次登录(以防万一它遭到破坏), 我们确保同一用户下次登录时, 需要重新签名。这是通过为此用户生成另一个随机随机数并将其持久保存到数据库来实现的。
等等!这就是我们管理现时签名无密码登录流程的方式。
为什么登录流程有效 根据定义, 身份验证实际上仅是帐户所有权的证明。如果你使用公共地址唯一地标识你的帐户, 那么证明你拥有该帐户在密码上并不重要。
为了防止黑客掌握一条特定的消息以及你对它的签名(而不是你的实际私钥), 我们强制将消息签名为:
- 由后端提供, 并且
- 定期更换
文章图片
让我们一起构建 在本节中, 我将一步一步地完成上述六个步骤。我将展示一些代码片段, 说明如何从头开始构建此登录流, 或将其集成到现有的后端中而无需花费太多的精力。
【一键式登录区块链(MetaMask教程)】出于本文的目的, 我创建了一个小型演示应用程序。我正在使用的堆栈如下:
- Node.js, Express和SQLite(通过Sequelize ORM)在后端实现RESTful API。成功认证后, 它将返回JWT。
- 在前端反应单页应用程序。
可以在此GitHub存储库中看到整个项目。演示在这里托管。
步骤1:修改用户模型(后端)
需要两个字段:publicAddress和nonce。我们将随机数初始化为随机大数。每次成功登录后, 都应更改此号码。我还在此处添加了一个可选的用户名字段, 以便用户可以更改。
const User = sequelize.define('User', {
nonce: {
allowNull: false, type: Sequelize.INTEGER.UNSIGNED, defaultValue: () =>
Math.floor(Math.random() * 1000000) // Initialize with a random nonce
}, publicAddress: {
allowNull: false, type: Sequelize.STRING, unique: true, validate: { isLowercase: true }
}, username: {
type: Sequelize.STRING, unique: true
}
});
为简单起见, 我将publicAddress字段设置为小写。更严格的实现将添加验证功能, 以检查此处的所有地址都是有效的以太坊地址。
步骤2:产生随机数(后端)
这是在上述模型定义中的defaultValue()函数中完成的。
第3步:用户获取其现值(前端)
下一步是在后端添加一些样板代码, 以处理User模型上的CRUD方法, 在此我们不再做。
切换到前端代码, 当用户单击登录按钮时, 我们的handleClick处理程序将执行以下操作:
class Login extends Component {
handleClick = () =>
{
// --snip--
const publicAddress = web3.eth.coinbase.toLowerCase();
// Check if user with current publicAddress is already present on back end
fetch(`${process.env.REACT_APP_BACKEND_URL}/users?publicAddress=${publicAddress}`)
.then(response =>
response.json())
// If yes, retrieve it. If no, create it.
.then(
users =>
(users.length ? users[0] : this.handleSignup(publicAddress))
)
// --snip--
};
handleSignup = publicAddress =>
fetch(`${process.env.REACT_APP_BACKEND_URL}/users`, {
body: JSON.stringify({ publicAddress }), headers: {
'Content-Type': 'application/json'
}, method: 'POST'
}).then(response =>
response.json());
}
在这里, 我们正在使用web3.eth.coinbase检索MetaMask活动帐户。然后, 我们检查此publicAddress在后端是否已经存在。如果用户已经存在, 我们将检索它;否则, 我们将在handleSignup方法中创建一个新帐户。
步骤4:用户对Nonce(前端)进行签名
让我们继续前进我们的handleClick方法。现在, 我们拥有一个由后端给定的用户(无论是检索到的还是新创建的)。特别是, 我们有他们的现时和publicAddress。因此, 我们准备使用web3.personal.sign用与此publicAddress关联的私钥对随机数进行签名。这是在handleSignMessage函数中完成的。
请注意, web3.personal.sign将字符串的十六进制表示形式作为其第一个参数。我们需要使用web3.fromUtf8将UTF-8编码的字符串转换为十六进制格式。另外, 我决定签署一个更加用户友好的句子, 因为它会显示在MetaMask确认弹出窗口中, 而不是仅对现时签名:我正在对我的一次性现时签名:$ {nonce}。
class Login extends Component {
handleClick = () =>
{
// --snip--
fetch(`${process.env.REACT_APP_BACKEND_URL}/users?publicAddress=${publicAddress}`)
.then(response =>
response.json())
// If yes, retrieve it. If no, create it.
.then(
users =>
(users.length ? users[0] : this.handleSignup(publicAddress))
)
// Popup MetaMask confirmation modal to sign message
.then(this.handleSignMessage)
// Send signature to back end on the /auth route
.then(this.handleAuthenticate)
// --snip--
};
handleSignMessage = ({ publicAddress, nonce }) =>
{
return new Promise((resolve, reject) =>
web3.personal.sign(
web3.fromUtf8(`I am signing my one-time nonce: ${nonce}`), publicAddress, (err, signature) =>
{
if (err) return reject(err);
return resolve({ publicAddress, signature });
}
)
);
};
handleAuthenticate = ({ publicAddress, signature }) =>
fetch(`${process.env.REACT_APP_BACKEND_URL}/auth`, {
body: JSON.stringify({ publicAddress, signature }), headers: {
'Content-Type': 'application/json'
}, method: 'POST'
}).then(response =>
response.json());
}
当用户成功签名消息后, 我们将转到handleAuthenticate方法。我们只需将请求发送到后端的/ auth路由, 发送我们的publicAddress以及用户刚刚签名的消息的签名。
步骤5:签名验证(后端) 这是稍微复杂一点的部分。后端在/ auth路由上收到包含publicAddress和签名的请求, 并且需要验证此publicAddress是否已签名正确的现时。
第一步是从数据库中检索具有所述publicAddress的用户;只有一个, 因为我们将publicAddress定义为数据库中的唯一字段。然后, 我们将消息msg设置为” 我正在签名…” , 就像在步骤4的前端中一样, 使用该用户的现时。
下一步是验证本身。有一些加密。如果你喜欢冒险, 建议你阅读更多有关椭圆曲线签名的信息。
总结一下, 此块的作用是, 给定我们的msg(包含随机数)和我们的签名, ecrecover函数输出用于对msg进行签名的公共地址。如果它与请求正文中的publicAddress相匹配, 则提出请求的用户将成功证明其对publicAddress的所有权。我们认为它们已通过验证。
User.findOne({ where: { publicAddress } })
// --snip--
.then(user =>
{
const msg = `I am signing my one-time nonce: ${user.nonce}`;
// We now are in possession of msg, publicAddress and signature. We
// can perform an elliptic curve signature verification with ecrecover
const msgBuffer = ethUtil.toBuffer(msg);
const msgHash = ethUtil.hashPersonalMessage(msgBuffer);
const signatureBuffer = ethUtil.toBuffer(signature);
const signatureParams = ethUtil.fromRpcSig(signatureBuffer);
const publicKey = ethUtil.ecrecover(
msgHash, signatureParams.v, signatureParams.r, signatureParams.s
);
const addressBuffer = ethUtil.publicToAddress(publicKey);
const address = ethUtil.bufferToHex(addressBuffer);
// The signature verification is successful if the address found with
// ecrecover matches the initial publicAddress
if (address.toLowerCase() === publicAddress.toLowerCase()) {
return user;
} else {
return res
.status(401)
.send({ error: 'Signature verification failed' });
}
})
身份验证成功后, 后端将生成JWT并将其发送回客户端。这是经典的身份验证方案, 你可以在存储库中找到将JWT与后端集成的代码。
步骤6:更改Nonce(后端) 出于安全原因, 最后一步是更改随机数。成功通过身份验证后, 请添加以下代码:
// --snip--
.then(user =>
{
user.nonce = Math.floor(Math.random() * 1000000);
return user.save();
})
// --snip--
并不难, 不是吗?同样, 如果你想查看整个应用程序的连接方式(JWT生成, CRUD路由, localStorage等), 请随时查看GitHub存储库。
今天就可以投入生产 尽管区块链可能存在缺陷, 并且仍处于起步阶段, 但我不能足够强调今天如何在任何现有网站上实现这种登录流程。这是一个参数列表, 为什么与电子邮件/密码登录和社交登录相比, 此登录流更可取:
- 增强的安全性:通过公钥加密的所有权证明比通过电子邮件/密码或第三方的所有权证明更安全-之所以如此, 是因为MetaMask将凭据存储在本地计算机上, 而不是在线服务器上, 这使得攻击面较小。
- 简化的用户体验:这是一次单击(好的, 也许是两次单击)的登录流程, 只需几秒钟即可完成, 而无需键入或记住任何密码。
- 更高的隐私性:无需电子邮件, 也无需第三方参与。
但是此登录流程并不适合所有人:
- 用户需要安装MetaMask:如果没有MetaMask或启用了Web3的浏览器, 此登录流程显然无法正常工作。如果你的听众对加密货币不感兴趣, 那么他们甚至很少考虑安装MetaMask。希望借助最近的加密技术繁荣, 我们正朝着Web 3.0互联网发展。
- 需要在后端完成一些工作:如我们所见, 实现此登录流程的简单版本非常简单。但是, 要将其集成到现有的复杂系统中, 需要在涉及身份验证的所有领域进行一些更改:注册, 数据库, 身份验证路由等。这尤其如此, 因为每个帐户将与一个或多个公共地址相关联。
- 它不适用于移动设备:请继续阅读本节。
对于移动应用程序, 答案是肯定的, 登录流程有效, 但是还有很多基础工作要做。基本上, 你需要自己重建一个简单的以太坊钱包。这包括公共地址生成, 种子词恢复和安全私钥存储, 以及web3.personal.sign和确认弹出窗口。幸运的是, 有一些库可以为你提供帮助。由于应用本身拥有私钥, 因此自然需要重点关注的关键领域是安全性。在桌面浏览器上, 我们将此任务委托给MetaMask。
因此, 我认为简短的答案是” 否” , 该登录流程在当今的移动设备上不起作用。人们正在朝这个方向努力, 但是今天的简单解决方案仍然是针对移动用户的并行传统登录方法。
让你的用户使用MetaMask登录 我们在本文中介绍了一种不涉及第三方的一键式加密安全登录流程, 称为” 使用MetaMask登录” 。我们解释了后端生成的随机随机数的数字签名如何证明帐户的所有权, 从而提供身份验证。我们还探讨了与台式机和移动设备上的传统电子邮件/密码或社交登录相比, 该登录机制的取舍。
即使今天这样的登录流程的目标受众仍然很小, 我也衷心希望你们中的一些人能激发灵感, 在你自己的Web应用程序中与传统的登录流程同时提供使用MetaMask登录-我很想听听如果你这样做。如有任何疑问, 请随时与我们联系。
相关:终极ENS和?App教程
推荐阅读
- 使用Flask REST API进行Python机器学习预测
- 如何解决机器学习问题
- 深入学习强化学习
- 以太坊Oracle合同(统一代码功能(2))
- 开发用于二硫键研究的生物信息学数据库
- Windows 10如何修复Firefox SSL_ERROR_NO_CYPHER_OVERLAP(解决办法)
- 前18名最佳风扇速度控制器软件下载推荐合集(哪个最好用())
- Windows的30大最佳IRC客户端下载推荐合集(哪个更好())
- 如何修复Windows更新错误0x80070057(解决办法教程)