Autenticação JSON Web Token (JWT) em Node.js - Parte 2

Node.js

Autenticação JSON Web Token (JWT) em Node.js - Parte 2

Luiz Duarte
Escrito por Luiz Duarte em 10/02/2020
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Atualizado em 10/02/2020!

JSON Web Token, o famoso JWT, é de longe a forma mais comum de segurança utilizada em web APIs RESTful depois do uso de SSL/TLS. Quando temos variados clientes consumindo nossas APIs, saber quem está autenticado ou não, ou ainda, que tem autorização ou não para fazer as chamadas, é importantíssimo.

Na primeira parte deste tutorial eu ensinei como implantar um mecanismo de JWT em uma API Node.js, adicionando esta importante camada de segurança. Dentre as dúvidas mais comuns acerca desta técnica está a segurança do token, afinal ele é o ponto mais exposto na técnica e sequestro de tokens é a forma mais comum de tentar burlar este mecanismo.

Claro que usando SSL (sugiro Lets Encrypt que é gratuito) esse risco de captura diminui consideravelmente uma vez que a conexão está criptografada e encorajo fortemente você a não aceitar requisições usando HTTP. No entanto, sabemos que para tudo existem brechas a serem exploradas principalmente por parte de pessoas maliciosas dentro da sua própria empresa muitas vezes…

Curso FullStack

O risco do JWT

Mas o token não é criptografado? Eu olhei ele e vi um monte de letras e números aleatórios…

Não, o token apenas é codificado em base64, uma representação textual de um conjunto de bytes, se você jogá-lo em qualquer decodificador online, verá as três partes que o compõem sendo que a única ilegível é a terceira onde temos a assinatura digital do servidor, atestando que aquele token foi gerado corretamente pelo seu servidor, o que impede que tokens fake se passem por tokens reais.

No entanto, para essa assinatura é necessário um segredo/secret. Esse segredo é usado tanto para assinar quanto para verificar a assinatura (e autenticidade) de um token. Se você tem apenas uma webapi que usa o JWT, isso é bem tranquilo. Agora, se você possui diferentes microservices e todos eles precisam de autenticação/autorização via JWT, então você tem um problema pois:

  • ou você faz com que o cliente se autentique em cada um dos microservices que vai usar;
  • ou você compartilha o secret entre todos eles.;

Uma solução possível para este problema é usar um API Gateway na frente de todos microserviços. Daí todos eles confiariam no gateway, sem ficar pedindo ou verificando tokens. Mas geralmente bons APIs gateways custam caro, embora dê pra fazer um mais caseiro.

Antes de eu entrar em uma possível solução barata para mitigar esse risco, vale lembrar que é importante você ter uma maneira fácil e rápida de invalidar tokens em caso de fraude de chamadas, para que a área de segurança possa agir rapidamente nestas situações. Falarei mais sobre isso no futuro.

Mas voltando ao assunto central: como podemos adicionar mais segurança em nosso JWT?

Entendendo o JWT

Para entendermos realmente o JWT na prática, primeiro compartilho abaixo uma pequena API que implementa este mecanismo, para que você possa acompanhar meu raciocínio sem precisar refazer todo tutorial anterior.

Para fazer funcionar este código, você terá de instalar os seguintes pacotes com o comando abaixo:

Se você rodar este projeto simples ele deve funcionar como esperado: a rota clientes só pode ser acessada com um JWT válido. Para obter um, POST na rota de login com usuário e senha corretos no corpo.

Se pegarmos o token abaixo…

E jogarmos em um decodificador de base64 online teremos…

A primeira parte é o header, a segunda é o payload e a terceira é a assinatura do servidor (um hash criado usando o header + payload + secret configurado no servidor). A assinatura é que realmente garante que este token não é forjado, mas para que ela possa ser verificada, a API deve conhecer o mesmo secret usado na assinatura. E isso é um problema de segurança, afinal, se você tem uma senha que muita gente conhece, ela não é uma senha segura…

Mas como podemos garantir que este token possa ser usado em diferentes microservices sem compartilhar o secret com todos eles?

Assinatura assimétrica do JWT

Como estou falando de um token que navega entre diferentes microservices por meio inseguro (internet) o ideal é uma criptografia assimétrica, onde o emissor/servidor irá gerar o token assinado com a chave privada (de posse somente dele) e os consumidores/clientes podem verificá-lo usando a chave pública (de posse de todos microservices).

Assim, todos microservices confiam na emissão de tokens a partir um servidor central, enquanto podem validar sua assinatura para garantir que não foi forjado, sem saber o secret original.

Primeiro, vamos criar um par de chaves (uma pública e outra privada) usando o algoritmo RSA, um dos mais famosos e seguros do mundo de tipo assimétrico. O jeito mais fácil de gerar um par para fins de estudo é usando um gerador online, como esse aqui. O Format Scheme é PKCS #1 e o tamanho da chave varia de 256 bits a 2048 e embora chaves maiores sejam mais seguras (cada vez que você dobra o tamanho multiplica por 6x a dificuldade de quebra da chave) atente ao fato de que seu JWT deverá ser decifrado pelo servidor a cada requisição e que chaves maiores são mais demoradas para decifrar, mesmo com a chave certa.

Como é apenas para estudo, fiz com o menor tamanho comercialmente aceito (1024, enquanto que 2048 é o mais recomendado até 2030) e minha chave pública ficou assim (salve em um arquivo public.key):

Enquanto que minha privada ficou assim (salve em um arquivo private.key):

Agora, em nossa API, vamos mudar levemente o nosso código que gera os tokens para que os mesmos sejam assinados de maneira assimétrica.

As alterações estão apenas a partir da linha que declaro a privateKey, que substitui o nosso secret padrão que estávamos usando. A leitura do arquivo da chave é feita usando módulo fs (adicione um require no topo do arquivo) e nas opções de assinatura (terceiro argumento da função) dizemos o algoritmo de hashing que o RSA vai usar no seu algoritmo interno (RS256 no meu caso represa SHA-256).

Atenção: caso o seu arquivo de chave secreta tenha senha (como são geralmente os .PEM gerados pelo OpenSSL, que é a ferramenta que recomendo que você use) você deve passar um objeto {key, passphrase} no segundo argumento do sign ao invés de apenas a privatekey.

Agora, ao se autenticar, o servidor lhe retornará um token com assinatura criptografada de forma assimétrica, o que permite que vários serviços possam verificar sua assinatura usando a chave pública, ao invés da chave privada, usada para assinar o token da primeira vez, o equivalente ao secret original.

Atenção: se você tiver o erro “digest too big for RSA key” quer dizer que sua chave é pequena demais para o texto a ser cifrado. Neste caso, use chaves maiores (eu não tive problema a partir de 1024-bit).

Verificando o JWT

Agora é hora de ajustar o código que verifica a assinatura do JWT. Enquanto que para criarmos a assinatura usamos a private key, para verificar a mesma usamos a public key.

Note que a alteração foi bem sutil mesmo: carregamos a public key a partir do respectivo arquivo, passamos ela pra função verify, bem como um objeto informando o algoritmo de hashing que usamos junto do RSA (RS256 refere-se a SHA-256). Sim, é um array de algoritmos e passei apenas um.

Agora, se você obter um token pela rota de login e usá-lo para acessar a rota de clientes, verá que está funcionando como deveria e que no uso comum de apenas um cliente e servidor, não mudou em nada. Mas se tiver que confiar neste token em diferentes microservices, todos eles podem verificar a assinatura usando a chave pública.

Note que esta abordagem consome mais recursos computacionais que os tokens comuns. Esteja preparado para um aumento custo de hardware e/ou do tempo entre cada requisição que necessita deste token.

Espero que tenha gostado do artigo e se gosta do assunto segurança da informação, o vídeo abaixo é para você!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu curso sobre programação web com Node.js!

Curso Node.js e MongoDB

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

2 Replies to “Autenticação JSON Web Token (JWT) em Node.js – Parte 2”

Gabriel

Excelente artigo! É o básico bem feito para garantir o mínimo de segurança na autenticação de microserviços.
Apenas um adentro, acredito que atualmente faça mais sentido utilizar authorization bearer ao invés de x-access-token para autorização do jwt!

Luiz Duarte

Sim, o cabeçalho mais comum de ser usado atualmente é o Authorization mesmo. Independente disso, o funcionamento é o mesmo. O mesmo vale para a notação ‘Bearer’ que não tem qualquer efeito prático ou diferente no funcionamento, é apenas uma convenção comum para dizer que o conteúdo do cabeçalho é um token do portador (Bearer).