web3服务端身份验证

概述“钱包登录”按钮的技术实现

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

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2022-03-25 16:22
  • 阅读 ( 1137 )
  • 学分 ( 32 )
  • 分类:以太坊

0 条评论

请先 登录 后评论
影无双
影无双

28 篇文章, 1502 学分