Meu nome é Nader Dabit. Trabalho como engenheiro de Relações com Desenvolvedores com Edge & Node e The Graph.
O código completo para este projeto está localizado aqui.
A autenticação Web3 é um dos tópicos que mais ressoam com os desenvolvedores que conheço quando se fala em web3.
Ao contrário dos serviços centralizados que coletam e rastreiam suas informações pessoais e perfis de usuários, na web3 podemos usar carteiras blockchain e criptografia de chave pública para nos identificar. Podemos então optar por vincular ou não nossa própria identidade a esse endereço usando protocolos como IDX ou ENS ou ter vários endereços para vários tipos de aplicativos e casos de uso.
Assinaturas criptográficas são usadas para provar a propriedade de um endereço. Podemos usar essas assinaturas para gravar transações em uma blockchain, mas também para assinar mensagens de qualquer tipo e decodificá-las em um servidor usando bibliotecas web3 como web3.js e ethers.js.
Usando esta técnica, podemos verificar se o remetente de uma mensagem é realmente autêntico, permitindo-nos realizar autenticação e autorização em apenas algumas linhas de código.
Neste post, quero mostrar como implementar isso em um aplicativo Next.js, criando um exemplo básico de como um fluxo de autenticação do mundo real pode ser. Também quero mostrar como configurar uma UI multiplataforma que permitirá que os usuários escolham entre várias carteiras que estão acessando no seu aplicativo de um navegador da Web ou móvel.
A maneira como faremos isso é usando a biblioteca ethers.js e os utilitários signMessage e VerifyMessage da biblioteca.
Por exemplo, podemos assinar uma mensagem de uma aplicação do lado do cliente usando a carteira blockchain do usuário assim:
const signature = await signer.signMessage(some_string)
Em seguida, verifique-o em um servidor usando uma combinação de assinatura, endereço e string:
const decodedAddress = ethers.utils.verifyMessage(some_string, signature)
if (decodedAddress === sender_address) {`
// assinatura verificada
}
Vamos ver como podemos implementar essa técnica em uma aplicação full stack, além de incluir outras partes importantes do fluxo de autenticação, como várias opções de carteira, e fazê-lo funcionar em várias plataformas.
Para usar o aplicativo que estamos prestes a construir, você precisará ter uma carteira web3 como Metamask ou Rainbow instalada em seu dispositivo ou como uma extensão em seu navegador da web.
Um salve para Rahat Chowdhury, cujo workshop de ss foi expandido para esta postagem no blog e para o projeto de exemplo.
Começando
Para começar, vamos primeiro criar um novo aplicativo Next.js:
npx create-next-app sign-in-with-ethereum
Em seguida, mude para o novo diretório e instale as seguintes dependências usando npm ou yarn:
npm instala ethers web3modal @walletconnect/web3-provider @toruslabs/torus-embed
Em seguida, na pasta pages/api crie um novo arquivo chamado auth.js e adicione o seguinte código:
import { users } from '../../utils/users'
export default function auth(req, res) {
const {address} = req.query
let user = users[address]
if (!user) {
user = {
address,
nonce: Math.floor(Math.random() * 10000000)
}
users[address] = user
} else {
const nonce = Math.floor(Math.random() * 10000000)
user.nonce = nonce
users[address] = user
}
res.status(200).json(user)
}
Esta é uma API de usuário básica que verificará se existe um usuário no registro. Se houver, atualizamos o nonce do usuário (que será usado para verificar o usuário na próxima etapa) e devolvemos as informações do usuário ao cliente. Se o usuário ainda não foi criado, criamos um novo usuário e atribuímos a ele um nonce.
Em seguida, crie um arquivo no diretório api chamado verify.js com o seguinte código:
/* pages/api/verify.js */
import { ethers } from 'ethers'
import { users } from '../../utils/users'
export default function verify(req, res) {
let authenticated = false
const {address, signature} = req.query
const user = users[address]
const decodedAddress = ethers.utils.verifyMessage(user.nonce.toString(), signature)
if(address.toLowerCase() === decodedAddress.toLowerCase()) authenticated = true
res.status(200).json({authenticated})
}
Este código é tudo o que precisamos para autenticar uma transação no servidor. A assinatura é criada no lado do cliente e enviada junto com a requisição, assinada com alguma string única, no nosso caso um nonce que foi gerado no servidor no passo anterior. Usamos esse nonce para decodificar a transação no servidor e, se o endereço da string decodificada corresponder ao endereço do chamador, podemos supor que a transação foi enviada pelo mesmo usuário.
Em seguida, crie uma pasta no diretório raiz chamada utils e crie um novo arquivo chamado users.js com o seguinte código:
/* utils/users.js */
export const users = {}
Este é um registro de usuário básico que provavelmente não é algo que você usaria em produção, mas nos permite simular como algo assim pode ser feito em um aplicativo do mundo real. O back-end para isso pode ser qualquer coisa, desde um protocolo descentralizado como Ceramic, ThreadDB ou GunDB, até um serviço centralizado ou BD.
Em seguida, vamos atualizar pages/index.js com o código do lado do cliente. Primeiro, remova todo o código existente e adicione as seguintes importações e declarações de variável:
/* pages/index.js */
import React, { useState } from 'react'
import { ethers } from 'ethers'
import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'
Aqui importamos o Web3Modal e o WalletConnect, o que permitirá que os usuários tenham uma boa variedade de opções para conectar sua carteira web3.
Em seguida, vamos criar a interface do usuário principal. (o código para este arquivo também está localizado aqui)
Você pode obter um ID de projeto Infura gratuitamente aqui. Se você não quiser se inscrever no Infura , pode deixar de fora o objeto
walletconnect
e ainda terá as opções Torus e Metamask.
/* pages/index.js */
const ConnectWallet = () => {
const [account, setAccount] = useState('')
const [connection, setConnection] = useState(false)
const [loggedIn, setLoggedIn] = useState(false)
async function getWeb3Modal() {
let Torus = (await import('@toruslabs/torus-embed')).default
const web3Modal = new Web3Modal({
network: 'mainnet',
cacheProvider: false,
providerOptions: {
torus: {
package: Torus
},
walletconnect: {
package: WalletConnectProvider,
options: {
infuraId: 'your-infura-id'
},
},
},
})
return web3Modal
}
async function connect() {
const web3Modal = await getWeb3Modal()
const connection = await web3Modal.connect()
const provider = new ethers.providers.Web3Provider(connection)
const accounts = await provider.listAccounts()
setConnection(connection)
setAccount(accounts[0])
}
async function signIn() {
const authData = await fetch(`/api/auth?address=${account}`)
const user = await authData.json()
const provider = new ethers.providers.Web3Provider(connection)
const signer = provider.getSigner()
const signature = await signer.signMessage(user.nonce.toString())
const response = await fetch(`/api/verify?address=${account}&signature=${signature}`)
const data = await response.json()
setLoggedIn(data.authenticated)
}
return(
<div style={container}>
{
!connection && <button style={button} onClick={connect}> Connect Wallet</button>
}
{ connection && !loggedIn && (
<div>
<button style={button} onClick={signIn}>Sign In</button>
</div>
)}
{
loggedIn && <h1>Welcome, {account}</h1>
}
</div>
)
}
const container = {
width: '900px',
margin: '50px auto'
}
const button = {
width: '100%',
margin: '5px',
padding: '20px',
border: 'none',
backgroundColor: 'black',
color: 'white',
fontSize: 16,
cursor: 'pointer'
}
export default ConnectWallet
A função connect solicitará que os usuários façam login com a carteira de sua escolha. Depois que eles se conectarem, o endereço da carteira será armazenado no estado local. Se eles estiverem em um dispositivo móvel, a WalletConnect permite que eles se autentiquem com este e que sejam redirecionados de volta ao aplicativo para continuar
A função signIn se conectará ao registro do usuário e gerará o nonce. Em seguida, ele retornará o nonce _ao cliente, permitindo que o usuário assine a transação com o _nonce. No servidor, verificaremos se o endereço recuperado da assinatura assinada com o nonce recuperado do servidor corresponde ao endereço do usuário. Se isso corresponder, o usuário será autenticado e atualizaremos a UI de acordo.
Testando
Para compilar e executar o aplicativo, execute o seguinte comando:
npm run build && npm start
Quando o aplicativo for carregado, você poderá fazer login com sua carteira ethereum.
O código completo para este projeto está localizado aqui.
Os royalties para este post serão divididos com Rahat Chowdhury, que é o eth-sso-workshop que expandi para este tutorial.
Próximos passos
Aqui estão alguns outros projetos para analisar se você estiver interessado em autenticação web3.
Ceramic e IDX. Além disso, aqui está um vídeo e um repositório mostrando como usá-lo.
Login Spruce com exemplo Ethereum SSR.
ENS - obtenha perfis de usuário do domínio ENS
Este artigo foi escrito por Nader Dabite traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Top comments (2)
Estou tendo problema na instalação da dependencia @walletconnect/web3-provider
não está encontrando repositorio =(
Desculpa pela demora, mas ja tentou consultar o github da lib?
github.com/walletconnect/walletcon...