WEB3DEV

Cover image for Autenticando-se com Ethereum: possuindo a sua própria identidade
Felipe Gueller
Felipe Gueller

Posted on • Atualizado em

Autenticando-se com Ethereum: possuindo a sua própria identidade

Banner da apresentação do tema: Autenticando-se com Ethereum

SIWE

Com a criptografia de chave pública subjacente ao Web3, um dos novos casos de uso que surgiram é a capacidade de verificar “quem você diz que é” sem a necessidade de um provedor de identidade centralizado. Embora existam muitas peças sendo construídas simultaneamente no espaço de identidade descentralizada em rápida evolução, a SpruceID está liderando a carga na frente de autenticação.

O SIWE (Sign In With Ethereum) (Entrar com Ethereum) rapidamente se estabeleceu como uma ferramenta essencial para usuários que exigem mais autonomia sobre sua própria identidade digital. Isso é obtido ao fornecer aos usuários a opção de autocustodiar sua própria identidade digital por meio de uma carteira compatível com EVM (ou seja, assinando uma mensagem verificável). Além disso, ao estabelecer um fluxo de trabalho de login padrão, o SIWE pode ser facilmente integrado aos serviços de identidade existentes e, portanto, não se limita apenas a aplicativos nativos Web3.

O SpruceID forneceu um exemplo de bloco de notas SIWE muito útil que implementa uma autenticação SIWE de ponta a ponta no Express.js, completa com gerenciamento de sessão.

O bloco de notas SIWE

siwe-notepad

O objetivo deste guia é focar apenas no fluxo de login principal usando a Metamask. Ao deixar de lado o gerenciamento de sessão, bem como a integração com outros provedores de carteira, espero simplificar os principais aspectos do SIWE:

  • Autenticação SIWE no Cliente vs Servidor
  • Construindo a mensagem de entrada para os usuários aprovarem
  • Evitando ataques de repetição por meio da sincronização de número que pode ser usado uma única vez (nonce)
  • Interagindo com o SIWE por meio da IU da Metamask

O repositório do Github para este guia pode ser encontrado aqui. Se você quiser uma introdução à interação programática com o Metamask:


Sumário

1 . Configurando um esqueleto de aplicativo

2 . Criando uma autenticação com o botão SIWE

3 . Usando o SIWE para entrar no navegador

4 . Atualizando os nossos Endpoints do Servidor

5 . Fazendo login com interface do usuário Metamask


Configurando um esqueleto de aplicativo

Primeiro precisamos criar um diretório de projeto e instalar o Express:

mkdir MetamaskSIWE
cd MetamaskSIWE
npm install express
Enter fullscreen mode Exit fullscreen mode

Com o pacote Express instalado, podemos utilizar o express-generator para configurar um modelo de aplicativo de forma rápida e conveniente. Antes de executar o express-generator, podemos visualizar as opções de configuração executando o seguinte comando:

npx express-generator --help
Enter fullscreen mode Exit fullscreen mode

Imagem mostrando as opções de configurações

Para nossos propósitos, queremos inicializar nosso projeto usando EJS, pois isso minimiza a necessidade de troca de contexto devido às suas semelhanças com HTML. Assim, executaremos o express-generator com o sinalizador de visualização EJS:

npx express-generator -v ejs
Enter fullscreen mode Exit fullscreen mode

Inicialização do projeto com EJS

Conforme instruído, também instalaremos as dependências necessárias:

npm install
Enter fullscreen mode Exit fullscreen mode

Por uma questão de conveniência, usaremos o nodemon para atualizar automaticamente nosso aplicativo sempre que fizermos alterações. O comando abaixo instalará o nodemon globalmente para que você possa usá-lo em seus outros projetos:

npm install -g nodemon
Enter fullscreen mode Exit fullscreen mode

Uma vez instalado, podemos iniciar nosso aplicativo da web executando o seguinte comando:

nodemon start
Enter fullscreen mode Exit fullscreen mode

Você poderá ver uma página de boas-vindas abaixo ao visitar a porta padrão em localhost:3000.

Imagem de boas-vindas no localhost:3000

O Express agora está configurado e podemos prosseguir e fazer as alterações necessárias para conectar o Metamask ao nosso exemplo do Express.

Criando uma autenticação com o botão SIWE

A primeira coisa que iremos fazer é alterar a visualização para que a página exibida seja mais intuitiva para a nossa proposta. Navegue para dentro do arquivo index.ejs localizado na pasta \views. Nós podemos alterar o <body> com o código abaixo:

 <body>
    <h1><%= title %></h1>
    <button id="login">Login with SIWE</button>
    <script src="javascripts/bundle.js"></script>
  </body>
Enter fullscreen mode Exit fullscreen mode

De acordo com as boas práticas do Metamask, incluímos um botão para o usuário inicializar a requisição de conexão. A requisição de conexão deve sempre ser inicializada pelo usuário e não no carregamento da página.

Adicionalmente, incluimos um script bundle.js no qual estaremos criando brevemente. Esse script carregará o código requerido pelo Metamask para se conectar na aplicação.

Por último, também alterarmos o título que é inserido na página, modificando o index.js dentro da pasta /routes. O res.render() alimenta os dados passados ​​na função para o nosso arquivo index.ejs a ser renderizado.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'SIWE Example' });
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Salvando o código acima, o nodemon teria atualizado automaticamente seu aplicativo e seu navegador deveria exibir o seguinte:

A visualização do exemplo da página SIWE

Usando o SIWE para entrar no navegador

Para utilizar a biblioteca SIWE, primeiro vamos instalá-la através do código:

npm install siwe
Enter fullscreen mode Exit fullscreen mode

Observe que o SIWE vem com o Ethers.js em suas dependências e, portanto, não há necessidade de instalar o Ethers.js novamente, caso contrário, haverá um conflito.

Como precisaremos executar este script no navegador, também usaremos o “Browserify]()https://browserify.org/ para solicitar os módulos no navegador do cliente. Consulte a seção Browserify do guia anterior para obter mais detalhes sobre isso.

Consequentemente, primeiro criaremos um arquivo de entrada, provider.js, que contém toda a lógica necessária para este guia. Esse arquivo será então “browserificado” com o arquivo de saída, bundle.js, sendo colocado no diretório /public/javascripts/.

touch provider.js
Enter fullscreen mode Exit fullscreen mode

Podemos então copiar e colar o seguinte código em nosso arquivo provider.js:

const { ethers } = require('ethers');
const { SiweMessage } = require('siwe');

const loginButton = document.querySelector('#login');

loginButton.addEventListener('click', async () => {
    /**
     * Obtenha o provedor e o signatário na janela do navegador
     */
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();

    /**
     * Obtenha a conta ativa
     */
    const [address] = await provider.listAccounts();
    console.log(`Address used to construct SIWE message: ${address}`);

    /**
     * Obtenha um nonce gerado aleatoriamente pela biblioteca SIWE. Este nonce
     * é adicionado na seção onde podemos verificá-lo ao entrar. Por questões de
     * segurança, isso é gerado no servidor.
     */
    const nonce = await fetch('/api/nonce').then(res => res.text());
    console.log(`Nonce returned from server stored on client: ${nonce}`);

    /**
     * Obtenha o id da cadeia
     */
    const chainId = (await provider.getNetwork()).chainId;
    console.debug(chainId);

    /**
     * Cria a mensagem para o objeto.
     */
    const message = new SiweMessage({
        domain: document.location.host,
        address,
        chainId,
        uri: document.location.origin,
        version: '1',
        statement: 'Metamask SIWE Example',
        nonce,
    });
    console.log(`SIWE message constructed in the client:`)
    console.debug(message);

    /**
     * Gera a mensagem a ser assinada e usa o provedor para pedir uma assinatura
     */
    const signature = await signer.signMessage(message.prepareMessage());
    console.log(`Signed message signature: ${signature}`);

    /**
     * Chama o endpoint  sign_in para validar a mensagem. Se for válida, o console
     * exibirá a mensagem do servidor.
     */
    await fetch('/api/sign_in', {
        method: 'POST',
        body: JSON.stringify({message, signature}),
        headers: { 'Content-Type': 'application/json' }
    }).then(async (res) => {
        const message = await res.text();
        console.log(JSON.parse(message).message);
    })

})
Enter fullscreen mode Exit fullscreen mode

Primeiro instanciamos nosso botão usando o seletor de consulta e adicionamos um ouvinte de evento de clique no botão. Uma vez clicado, algumas coisas acontecerão:

  • Extraia o provider e o signer da janela do navegador usando Ethers.js. Isso nos permite interagir programaticamente com nossa instância Metamask.

  • Obtenha a conta Metamask ativa. Este será o endereço vinculado à solicitação de entrada.

  • Gere um nonce aleatório que será usado para validar o login entre o servidor e o cliente. Observe que esta NÃO é a conta nem o nonce de consenso usado no Ethereum. Para evitar que o nonce seja manipulado, ele é gerado no servidor chamando o endpoint /api/nonce.

Gerando um nonce aleatório

  • Crie uma nova mensagem SIWE que nos permitirá chamar os métodos da biblioteca SIWE na mensagem. Você pode encontrar o código aqui.

  • Solicite ao usuário que assine a mensagem SIWE preparada, que gerará um recibo contendo a assinatura bruta da mensagem. Você pode consultar a função signMessage() do Ethers.js aqui.

  • Chame o endpoint /api/sign_in para que o servidor valide e manipule o login. De acordo com as práticas recomendadas de gerenciamento de sessão, usaremos o método POST aqui para que informações confidenciais sejam incorporadas ao corpo da solicitação.

Salve o arquivo provider.js e podemos executar o comando Browserify:

browserify providers.js -o public/javascripts/bundle.js
Enter fullscreen mode Exit fullscreen mode

Um arquivo bundle.js será criado no diretório público a partir do qual os recursos estáticos são servidos. Observe que nosso arquivo index.ejs também contém uma tag de script que faz referência a esse caminho. Embora o nodemon tenha atualizado automaticamente nosso aplicativo, ainda precisamos implementar /api/nonce e /api/sign_in antes que nosso login esteja totalmente funcional.

Atualizando os nossos Endpoints do Servidor

Agora vamos mudar para nosso arquivo index.js localizado no diretório routes/ para adicionar nossos 2 endpoints.

O terminal /api/nonce utilizará a função generateNonce() da biblioteca SIWE, que gera um nonce suficientemente aleatório para evitar ataques de repetição. O código para generateNonce() pode ser encontrado aqui. Nosso endpoint responderá com o nonce gerado:

const { SiweMessage, generateNonce } = require('siwe');

// Armazenando o nonce para checar novamente no login.
let nonce;

// Gerando o nonce
router.get('/api/nonce', async (req, res) => {
  nonce = generateNonce();
  console.log(`Nonce generated on server: ${nonce}`);
  res.send(nonce);
})
Enter fullscreen mode Exit fullscreen mode

Observe que também inicializamos o nonce no nível do arquivo, em vez de salvá-lo em uma sessão para fins de legibilidade. O nonce é armazenado no servidor para poder compará-lo com o nonce retornado do cliente quando um usuário faz login.

A maior parte do código de login será tratada pelo endpoint /api/sign_in:

// Manipula a sessão no servidor Express
router.post('/api/sign_in', async (req, res) => {
  /**
    Obtenha a mensagem e a assinatura da requisição
   */
  const { message, signature } = req.body;

  /**
   * Formata a mensagem
   */
  const messageSIWE = new SiweMessage(message);

  /**
   * Cria uma instância do provedor padrão
   */
  const provider = ethers.getDefaultProvider();

  /**
   * Valida a mensagem SIWE vinda do cliente
   */
  const fields = await messageSIWE.validate(signature, provider); // pacote npm siwe para implementar a verificação
  if (fields.nonce !== nonce) {
    res.status(422).json({
      message: "Invalid nonce: Client and Server nonce mismatch"
    });
    return;
  }
  console.log(`SIWE message `)
  console.debug(fields);
  console.log(`Successfully logged in on the server`)

  /**
   * Retorna a mensagem de sucesso para o cliente
   */
  res.status(200).json({
    message: "Successfully logged in!"
  })
}
Enter fullscreen mode Exit fullscreen mode

De acordo com nosso código de cliente, esse endpoint é implementado como uma solicitação POST para minimizar vazamentos de dados (isso terá que ser emparelhado com HTTPS para melhor segurança). Consequentemente, primeiro precisamos desestruturar a solicitação para obter a mensageme a assinatura. A mensagem é então formatada no equivalente do SiweMessage. Esta SiweMessage deve conter as mesmas informações da gerada no navegador.

Para validar o par de assinatura e mensagem, também exigimos que uma nova instância do provedor seja criada. Nesse caso, usaremos o provedor padrão Ethers.js, pois precisamos apenas de uma maneira rápida de testar a função de entrada.

O login do usuário é então validado usando o método validate() no equivalente do SiweMessage. A implementação mais recente pode ser encontrada no Github da SIWE. Observe que a validate() será obsoleta em breve em favor da verify(), que implementa proteções adicionais. No entanto, como esta última alteração ainda não foi implementada no NPM, este guia ainda usará a função validate().

Após a validação, os resultados são gravados nos campos. De maneira crítica, nosso código também implementa uma verificação para garantir que fields.nonce seja equivalente ao nonce que foi gerado antes da chamada do /api/nonce. Depois que essa verificação for concluída, o usuário fez o login com êxito e podemos lidar com a sessão de acordo. Além disso, uma resposta de sucesso também é retornada ao cliente, que será registrada no console do navegador.

Fazendo login com interface do usuário Metamask

Agora podemos navegar em nosso navegador para testar o login por meio da interface do usuário. Clique no botão “Login with SIWE” e você deverá receber uma solicitação de assinatura da Metamask.

Imagem do código e da solicitação de assinatura da Metamask

Observe que a mensagem definida em nosso provider.js é exibida para o usuário revisar. Além disso, você também verá o console imprimindo detalhes relevantes, como o nonce e o SiweMessage.

Mensagem definida no provider.js para o usuário revisar

Você também deve ver o nonce equivalente sendo gerado e impresso nos logs do servidor.

Nonce sendo gerado e impresso

Podemos então clicar no botão “Sign” para aprovar esta transação. Isso criará a mensagem assinada que é impressa no console do navegador.

Exemplo da impressão da mensagem assinada

Com essa assinatura, o endpoint /api/sign_in também será acionado, resultando na impressão do seguinte no console do servidor:

mensagem mostrada no _console_ do servidor

Depois de concluído, a mensagem de sucesso retornada pelo servidor será refletida de acordo no console do navegador:

Mensagem dizendo que o _login_ foi efetuado com sucesso

Parabéns, você implementou com sucesso o login com Ethereum usando Metamask!

Obrigado por ficar até o fim. Adoraria ouvir seus pensamentos/comentários, então deixe um comentário. Estou ativo no twitter @AwKaiShin se você quiser receber informações mais digeríveis sobre informações relacionadas à criptografia ou visite meu site pessoal se quiser meus serviços :)

Este artigo é uma tradução de Aw Kai Shin feita por Felipe Gueller. Você pode encontrar o artigo original aqui.

Top comments (0)