Tenho visto alguns artigos recentemente sugerindo que o advento da Web3 e o uso generalizado de carteiras levarão os usuários a abandonar os sistemas de contas tradicionais baseados em e-mail/senha e, em vez disso, fazer login usando suas carteiras.
Para ser honesto, depois de usar alguns dApps, o fluxo de trabalho super simples de um ou dois cliques em sua carteira é realmente uma experiência superior.
Muitos desses artigos dizem “oba, não precisamos mais de JWTs!”.
É aqui que discordo.
Os JWTs, embora um pouco confusos no início, são incrivelmente úteis para engenheiros de back-end, especialmente aqueles de sistemas de microsserviços. Principalmente , considerando que esses sistemas - um grande número deles - já existem e já se integram com JWTs! Ethereum é ótimo e tudo, mas realmente não precisamos reinventar a roda. É bom poder continuar usando as mesmas ferramentas de back-end com as quais você está acostumado quando precisar.
Fazer login com MetaMask prova que é você - mas como você prova para futuras chamadas de API que é você?
A multidão “parafuso-JWT” sugere o uso de IDs de sessão simples, mas isso é um retrocesso para o início dos anos 2000, lembre-se, quando arquiteturas simples em camadas eram o padrão, que não tinham as complexidades dos sistemas de back-end de hoje.
Infelizmente, os IDs de sessão não podem ser verificados sem uma ida e volta adicional ao banco de dados para determinar se o ID de sessão concedido pertence ou não a uma sessão válida. Isso significa que, quando o serviço de back-end recebe a solicitação que contém o ID da sessão, ele faz uma solicitação ao servidor de autenticação perguntando "está ativo" - e todos os outros serviços no sistema Microservice perguntam o mesmo.
Se houver vários serviços envolvidos, isso pode significar várias idas e vindas adicionais ao serviço de autenticação.
Para remediar esta situação, os especialistas em criptografia começaram a pensar.
O que eles criaram foram JWTs - agora parte do padrão OpenID, você sabe, aquele que Keycloak, Auth0 e outros ajudam você a implementar.
A solução foi conceder um conjunto de tokens - JSON Web Tokens para ser mais preciso. Esse conjunto consiste em um AccessToken
, um RefreshToken
e um IdToken
. Esses tokens foram então “assinados” por um segredo - geralmente chamado de ClientSecret
. A assinatura, só para estarmos na mesma página, é um algoritmo de hash criptográfico, no caso de JWTs – geralmente HS256
(o padrão para Auth0). No caso do HS256
o ClientSecret
é usado como entrada e, portanto, torna-se a chave necessária para decifrar com sucesso esse hash - ou "verificá-lo". Com RS256
e ES256
um par de chaves pública/privada é usado, ou seja, assinado por chave privada e verificado com chave pública no cliente.
Isso significa que, se um serviço de back-end receber um desses tokens e conhecer o ClientSecret
, ele poderá verificar se o token foi realmente emitido pelo serviço de autenticação que assinou esse token. O token usado ao tentar acessar um serviço de back-end é o AccessToken
. Esses tokens contêm especificamente informações sobre quem é o usuário, bem como suas “reivindicações”, ou seja, o que eles podem fazer formatado para o sistema que se preocupa com isso.
Para sistemas de microsserviço, isso significa que cada serviço que se preocupa em verificar a identidade só precisa saber sobre o ClientSecret
, pois eles podem verificar se o JWT é autêntico usando-o, em vez de uma viagem de ida e volta ao banco de dados. Em um sistema com muitos microsserviços, isso pode reduzir muitas idas e vindas adicionais ao banco de dados, tornando todo o sistema mais escalável.
Os tokens de acesso, se roubados, podem ser usados de forma maliciosa e, por isso, é importante tomar as devidas precauções ao projetar um sistema para usá-los.
O conjunto mínimo de precauções, além de assinar e verificar os tokens, consiste em:
Definir uma data de expiração no Access Token JWT de 5 a 15 minutos e garantir que os tokens não expirem quando recebidos
Não armazene o token de acesso, exceto na memória
Emitir tokens de atualização que podem ser armazenados e enviados a um servidor para verificação e atualizados junto com o uso de ClientSecret
e ClientId
que o emitiram.
Use somente quando transmitido por conexões TLS (HTTPS)
Use cookies HTTP Only para que eles não possam ser modificados no lado do navegador
Configurações do CORS
Uma chave do mesmo tamanho da saída hash (por exemplo, 256 bits para "HS256") ou maior DEVE ser usada com este algoritmo. - RFC7518 , nota: Auth0 usa 512 bits para HS256.
Um cliente confidencial precisa de um servidor intermediário, como o Node.js - esse servidor é um proxy para o serviço de autenticação, portanto, o cliente não precisa saber sobre o segredo do cliente. Um cliente público expõe o segredo do cliente e não há um servidor proxy entre o navegador e o serviço de autenticação. Isso pode ser restringido ainda mais com as configurações do CORS, de modo que apenas solicitações de determinados domínios sejam permitidas.
Precauções adicionais incluem:
E provavelmente mais - por exemplo, se você detectar alguém tentando usar um token de atualização revogado, poderá revogar todos os tokens de atualização ativos desse usuário.
Todas essas precauções, reconhecidamente, podem tornar um pouco difícil acertar. Há muito o que entender. Também há preocupações de usabilidade. Um usuário que se deslogou do seu site a cada 5 minutos seria muito irritante para ele. Para evitar isso, um loop de atualização silenciosa deve ser implementado no aplicativo de consumo para atualizar continuamente o conjunto de tokens.
Dito isso, do outro lado de acertar, é ser capaz de integrar com segurança todos os seus sistemas de back-end de maneira escalável, bem como muitas ferramentas existentes - como Hasura , que pode gerar automaticamente todas as suas APIs para você com base em um esquema de banco de dados do Postgres conectado. Assim, ser capaz de integrar-se facilmente com ferramentas existentes pode economizar muito tempo de desenvolvimento.
Se você já usa o OpenId, é provável que já tenha essas coisas em vigor. Afinal, é um padrão de autenticação.
Então, como podemos manter a conveniência de usar JWTs em um Web3 e fazer login com o mundo MetaMask?
Vamos começar entendendo o fluxo de autenticação OpenId usado para SSO.
No mundo web3auth, estamos substituindo a etapa dois pelo uso dos pares de chaves pública e privada de sua carteira para assinar um desafio. O redirecionamento não é necessário ou desejado.
Você visita um site e deseja fazer login com sua conta - você clica no botão de login
Você recebe um desafio do servidor de autenticação que abre sua carteira pedindo para você “assinar” o desafio. Pressione o sinal.
O servidor de autenticação verifica sua assinatura e emite um conjunto de JWTs que são armazenados adequadamente e usados em fluxos de segundo plano de atualização silenciosa.
Estamos simplesmente substituindo o fluxo de redirecionamento do estilo SSO por um desafio assinado por sua carteira. O fluxo após o recebimento dos tokens permanece o mesmo do OpenID. Isso significa que você poderia, por exemplo, mudar de usar OpenID para usar web3auth com um servidor emissor de JWT, e nada sobre o uso desses tokens depois que eles forem concedidos precisaria mudar. Todas as suas integrações de back-end existentes com ferramentas como Hasura permanecem exatamente as mesmas.
Isso é exatamente o que eu quero como desenvolvedor Full Cycle. Não quero reinventar a roda. Quero substituir o OpenID pelo web3auth e ainda poder usar todas as ferramentas poderosas com as quais estou acostumado.
Infelizmente, não consegui encontrar um servidor web3auth que fizesse isso, além das precauções de segurança. Encontrei alguns projetos demonstrando técnicas no processo, mas não todo o fluxo de ponta a ponta.
Então eu comecei a construir…
Eu construí este servidor de autenticação aqui: https://github.com/CloudNativeEntrepreneur/web3auth-service
E aqui está a integração do SvelteKit para acompanhar, que implementa todas as coisas - atualização silenciosa, yada yada - todas as coisas que mencionei acima: https://github.com/CloudNativeEntrepreneur/sveltekit-web3auth
É claro que, se haveria um cliente GraphQL e um exemplo, também seria necessário um servidor e banco de dados GraphQL, então também forneci exemplos disso: https://github.com/CloudNativeEntrepreneur/example-hasura + https: //github.com/CloudNativeEntrepreneur/example-readmodel
Este exemplo usa o operador Zalando Postgres e SchemaHero , portanto, tudo o que você precisa fazer é declarar seus bancos de dados e descrever seu esquema em YAML, e o Hasura gerará automaticamente todas as APIs GraphQL necessárias. E, eu criei o servidor de autenticação com Hasura em mente, para que ele tenha as declarações adequadas para se integrar ao RBAC e às permissões de Hasura, que são bastante robustas.
E, claro, você precisa de um local para executar tudo isso e, portanto, um cluster de desenvolvimento local que configure todas as ferramentas, como istio, operadores e SchemaHero para você! https://github.com/CloudNativeEntrepreneur/local-dev-cluster
Mas quem sabe usar tudo isso?
É por isso que fiz este meta repo: https://github.com/CloudNativeEntrepreneur/web3auth-meta
O uso desse meta repo clonará todos os projetos de que você precisa nos lugares certos e os executará todos juntos.
Por fim, para rodar todos os projetos juntos, você precisa de ferramentas instaladas, e instalar ferramentas é chato - então fiz esse repo aqui que vai instalar todas elas para você! https://github.com/CloudNativeEntrepreneur/onboard
Também publiquei sveltekit-web3auth
no npm e criei um modelo de um projeto SvelteKit que o usa e tem o GraphQL configurado e integrado com autenticação a uma instância Hasura, então quando você estiver pronto para fazer seus próprios projetos, você pode usar isso como modelo! https://github.com/CloudNativeEntrepreneur/sveltekit-web3auth-template
Se você ainda não está pronto para o mundo de autenticação web3, também pode usar https://github.com/CloudNativeEntrepreneur/sveltekit-oidc , que vem pré-configurado para se conectar ao seu cluster de desenvolvimento local e uma instância do Keycloak configurada dentro isto. Vendo como ambos os projetos emitem JWTs, o objetivo é que o sistema de autenticação seja intercambiável - use web3auth ou OIDC clássico - o uso upstream dos tokens é o mesmo.
Agora vá e faça alguns dApps híbridos com APIs GraphQL geradas automaticamente e RBAC robusto e permissões e assinaturas e páginas SSR autenticadas e loops de atualização silenciosa e tokens de atualização rotativos e outras coisas!
Concluindo, não, JWTs e login com Ethereum/metamask não são mutuamente exclusivos. Na verdade, se você gosta da produtividade do desenvolvedor e da integração com as ferramentas existentes, acho que se sairá muito bem usando JWTs E web3auth.
Felicidades!
Estou disponível para consultoria! Se você estiver interessado em minha ajuda em um projeto em que está trabalhando, envie-me uma mensagem no Twitter!