web3服务端身份验证

2022-04-11 13:41:00 浏览数 (1)

本文作者:影无双[1]

DApp 最大的吸引力就是用户拥有自己的数据。然而要做到这一点,需要验证用户的 web3 身份(用户的钱包)。这在客户端是很容易的(因为用户可以用 Metamask 提交自己的信息),但是在服务端就没那么容易了。

在这篇文章中,我将概述“钱包登录”按钮的技术实现,类似Showtime[2]或者Foundation[3]的按钮。

从钱包到服务端

第一部分实现非常简单,让用户将钱包连接到我们的前端,并且从获取的钱包地址向服务端发送一个 API 请求。

这里的问题是,任何人都可以用别人的地址向我们发送 API 请求,并且我们无法验证这个地址是否映射到与前端的钱包。

在服务端验证签名

容易忽略的一点,本质上加密钱包只是一个密钥对(私钥和公钥的组合)。当你创建一笔交易,你仅仅是签署了交易参数(以数学方式证明你是创建者)并且将它广播到 ETH 网络上。

幸运的是,交易并不是钱包唯一可以签名的东西。我们可以创建任意一条消息(如Please sign this message to connect to Foundation.),并且验证签名,以确保验证身份的钱包就是签署消息的钱包。

以太坊签名是以Ethereum Signed Message:开头的 Keccak (SHA-3)哈希。我们可以在任何程序语言中用 Keccak 和 ECC (椭圆曲线密码学) 库进行验证。

我们需要三样东西来验证:要验证的地址、要签名的消息和签名,我们可以用任何 web3 库获取签名(下面例子用的ethers.js ):

代码语言:javascript复制
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 来验证。

代码语言:javascript复制
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 实现[4]

防止签名被利用

我们有一个可以用钱包登录的系统,和一套确保只能本人验证的方法。但是有一个问题,因为我们总是签名相同的消息,任何一个签名都是账户的永久密钥,永不过期。

这意味着,如果有人通过 MITM 攻击或欺骗我们在别的网站签署相同的消息来拦截它,他们将获得不可撤销的永久访问权限。

为了防止这样的事情发生,我们需要确保每次的消息都不同。最简单的方法就是生成一个随机字符串(nonce)包含到消息中。

我们首先需要在服务端生成 nonce ,并将其存储在会话中(因为之后需要它来验证签名):

代码语言:javascript复制
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.nnSecurity code (you can ignore this): ${req.session.nonce}`)
}

然后,不是硬编码要签名的消息,而是通过 AJAX 从服务端检索它:

代码语言:javascript复制
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[5],如果你正在用 PHP 和 Laravel ,我建议用 and laravel-web3-login[6]。如果你发现其他语言包,请私信我[7]

原文链接:https://m1guelpf.blog/VBlaOTPAxQNFHjl5n-C3BvhkpizvAui9A4RJDwFiQ3k

参考资料

[1]

影无双: https://learnblockchain.cn/people/58

[2]

Showtime: https://tryshowtime.com

[3]

Foundation: https://foundation.app

[4]

我的签名验证的 PHP 实现: https://github.com/m1guelpf/laravel-web3-login/blob/

[5]

passport-web3: https://github.com/coopermaruyama/passport-web3

[6]

laravel-web3-login: https://github.com/m1guelpf/laravel-web3-login

[7]

私信我: https://twitter.com/m1guelpf

0 人点赞