概述“钱包登录”按钮的技术实现
DApp 最大的吸引力就是用户拥有自己的数据。然而要做到这一点,需要验证用户的 web3 身份(用户的钱包)。这在客户端是很容易的(因为用户可以用 Metamask 提交自己的信息),但是在服务端就没那么容易了。
在这篇文章中,我将概述“钱包登录”按钮的技术实现,类似Showtime或者Foundation的按钮。
第一部分实现非常简单,让用户将钱包连接到我们的前端,并且从获取的钱包地址向服务端发送一个API请求。
这里的问题是,任何人都可以用别人的地址向我们发送API请求,并且我们无法验证这个地址是否映射到与前端的钱包。
容易忽略的一点,本质上加密钱包只是一个密钥对(私钥和公钥的组合)。当你创建一笔交易,你仅仅是签署了交易参数(以数学方式证明你是创建者)并且将它广播到ETH网络上。
幸运的是,交易并不是钱包唯一可以签名的东西。我们可以创建任意一条消息(如Please sign this message to connect to Foundation.
),并且验证签名,以确保验证身份的钱包就是签署消息的钱包。
以太坊签名是以Ethereum Signed Message:
开头的Keccak (SHA-3)哈希。我们可以在任何程序语言中用 Keccak 和 ECC (椭圆曲线密码学) 库进行验证。
我们需要三样东西来验证:要验证的地址、要签名的消息和签名,我们可以用任何 web3 库获取签名(下面例子用的ethers.js
):
import axios from 'axios'
import { ethers } from 'ethers'
// On production, you should use something like web3Modal
// to support additional wallet providers, like WalletConnect
const web3 = new ethers.providers.Web3Provider(window.ethereum)
const message = "Sign this message to log in to our app"
await axios.post('/api/auth/login', {
address: await web3.getSigner().getAddress(),
signature: await web3.getSigner().signMessage(message),
})
在服务端,我们可以用eth-sig-util
来验证被提交钱包所签名的消息,并且通过 cookie 或者 API token 来验证。
import { recoverPersonalSignature } from 'eth-sig-util'
const message = "Sign this message to log in to our app"
if (address.toLowerCase() !== recoverPersonalSignature({ data: data, sig: signature }).toLowerCase()) {
throw new Error('Authentication failed')
}
// wallet address has been verified, set a cookie (or return a token)
如果你想更好的掌握验证背后是如何工作的,你可以查看 我的签名验证的 PHP 实现
我们有一个可以用钱包登录的系统,和一套确保只能本人验证的方法。但是有一个问题,因为我们总是签名相同的消息,任何一个签名都是账户的永久密钥,永不过期。
这意味着,如果有人通过 MITM 攻击或欺骗我们在别的网站签署相同的消息来拦截它,他们将获得不可撤销的永久访问权限。
为了防止这样的事情发生,我们需要确保每次的消息都不同。最简单的方法就是生成一个随机字符串(nonce)包含到消息中。
我们首先需要在服务端生成 nonce ,并将其存储在会话中(因为之后需要它来验证签名):
import crypto from 'crypto'
export default async function(req, res) {
req.session.nonce = crypto.randomInt(111111, 999999)
res.end(`Hey! Sign this message to prove you have access to this wallet. This won't cost you anything.\n\nSecurity code (you can ignore this): ${req.session.nonce}`)
}
然后,不是硬编码要签名的消息,而是通过 AJAX 从服务端检索它:
import axios from 'axios'
import { ethers } from 'ethers'
// On production, you should use something like web3Modal
// to support additional wallet providers, like WalletConnect
const web3 = new ethers.providers.Web3Provider(window.ethereum)
const message = await axios.get('/api/auth/nonce').then(res => res.data)
await axios.post('/api/auth/login', {
address: await web3.getSigner().getAddress(),
signature: await web3.getSigner().signMessage(message),
})
最后,在检查签名之前,我们需要从会话中将 nonce 提取出来,从而重建消息。
有一些软件包可以处理这些事情。我建议在 Node 上用passport-web3,如果你正在用 PHP 和 Laravel ,我建议用and laravel-web3-login。如果你发现其他语言包,请私信我
原文链接:https://m1guelpf.blog/VBlaOTPAxQNFHjl5n-C3BvhkpizvAui9A4RJDwFiQ3k
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!