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

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…

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.

Desta forma, se o seu token for capturado (de alguma maneira mirabolante), durante o seu prazo de validade, ele poderá ser usado para fazer chamadas em seu nome e isso certamente não é um risco que você queira correr principalmente em aplicações mais visadas, como soluções bancárias (com as quais trabalho desde 2017).

Antes de eu entrar na solução 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?

Comprovando a vulnerabilidade do JWT

Para comprovarmos isso 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 token e a terceira é a assinatura do servidor (criado usando o secret/segredo configurado no servidor). Note a informação sensível do id no token, mas que poderia ter outras informações também, relacionadas ao cliente que está consumindo a API como perfil de acesso e outros.

Mas como podemos garantir que este token não possa mais ser lido de maneira aberta como essa?

Criptografando o JWT

A solução mais prática para adicionar mais proteção aos eu token é criptografá-lo. Como estou falando de um token que navega entre cliente e servidor por meio inseguro (internet) o ideal é uma criptografia assimétrica, onde o cliente usará a chave pública do servidor para lhe mandar o token, que só poderá ser decifrado com a chave privada, de posse SOMENTE do servidor.

Assim, mesmo que um token seja capturado, o seu conteúdo não poderá ser decifrado sem conhecer esta chave privada.

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 criptografados.

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 criptografado, muuuito mais difícil de ter suas informações decifradas pois para isso seria necessário descobrir a outra chave. Neste caso não estamos mais usando base64 e se tentar usar um decodificador online vai ver que ele não consegue ler nada.

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 decifra o JWT, feito na mesma função que verifica o mesmo. Enquanto que para crifrarmos o token usamos a private key, para verificar o mesmo 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, mas que o seu JWT está finalmente seguro caso seja capturado por algum atacante.

Note que esta abordagem consome mais recursos computacionais que os tokens abertos. 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!

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

Lidando com SOAP em Node.js

Se você começou a programar nos últimos 5 anos talvez nem saiba do que estou falando. Ou se sabe, é culpa de algum sistema legado com o qual você tem de lidar. Não te culpo por isso e “tamo junto”.

SOAP, ou Simple Object Access Protocol (não confundir com SOA), é um protocolo para criação de serviços web distribuídos e descentralizados, os famosos web services, que permitem a troca de informações entre sistemas, usando o formato XML. Eu não sei quando isso começou, mas a versão mais recente é de 2007 (1.2) e foi a forma mais popular de conectar sistemas na primeira década dos anos 2000, antes do surgimento do protocolo REST que incentivou as web APIs com JSON e mais tarde os micro serviços, formas mais leves para trocar informações entre sistemas pela web.

Todas as tecnologias web e mobile mais recentes oferecem suporte nativo (ou próximo disso) para lidar com Web APIs REST, mas pouco se fala de conexões com SOAP. O intuito deste tutorial é justamente ajudar quem precisa lidar com Node no client e SOAP no server.

#1 – Preparando o projeto

Este tutorial é intermediário e espera que você já saiba programar em Node.js. Caso ainda não possua esse conhecimento básico, conheça meus livros e meu curso.

Dito isso, crie um novo projeto na sua máquina com o nome de projeto-soap, crie um index.js vazio, rode um npm init para criar o package.json e instale a dependência do pacote Node SOAP que vamos utilizar, como abaixo:

Neste projeto, vamos criar um cliente que vai consumir um Web Service SOAP aberto e gratuito, disponível na Internet. Vou usar aqui, para fins de estudo o WS Dilbert, disponibilizado publicamente pela GComputer, cujo endereço é http://www.gcomputer.net/webservices/dilbert.asmx, mais especificamente o Web Method Daily Dilbert, que espera uma data como argumento e retorna uma frase como resposta, do famoso cartoon Dilbert.

Se você não está familiarizado com a documentação de Web Services SOAP, não é muito difícil de entender, desde que esteja minimamente acostumado com XML. Basicamente cada requisição, que pode ser enviada via HTTP (mais comum) ou RPC, é envelopada com seus argumentos e meta-informações em uma estrutura XML e retornada no mesmo formato, com seu valor de retorno e meta-informações. Por utilizar o formato XML, requests/responses SOAP geralmente são mais onerosos que seus equivalentes RESTful com JSON.

Estrutura SOAP
Estrutura SOAP

Para que clientes web consigam se comunicar com web services é necessário que eles entendam essa estrutura de comunicação XML, o que se faz através da leitura e interpretação do WSDL, ou Web Service Description Language, que fica disponível sempre no mesmo endereço do WS, apenas colocando o respectivo parâmetro no final: http://www.gcomputer.net/webservices/dilbert.asmx?wsdl

Dito isso e com a URL do WSDL em mãos, vamos iniciar a codificação do nosso cliente Node.

#2 – Criando o SOAP client

Fazer um SOAP client usando o pacote node-soap é muito fácil e nem dá para acreditar na trabalheira que é fazer sem um pacote profissional como esse.

Vamos começar criando um cliente através da conexão da biblioteca com o endereço do WSDL do nosso webservice, como abaixo:

Este código é bem simples e auto-explicativo, onde carregamos o pacote soap, a URL do WSDL e usamos a function createClient passando a url pra ela. Se tudo der certo, deve imprimir o conteúdo do objeto client no console.

O próximo passo, uma vez que temos um cliente SOAP instanciado e configurado para o WSDL de um web service em questão, é chamarmos algum de seus web methods. Ao acessar a página do web service encontramos dois deles, como mostra na imagem abaixo.

Web Service Dilbert
Web Service Dilbert

Temos dois WebMethods neste Web Service, o DailyDilbert e o TodaysDilbert (atenção à caixa alta e baixa, para chamar corretamente). Nós vamos usar o primeiro deles que, se clicarmos no mesmo (ou consultando diretamente o WSDL) vemos que ele espera apenas um parâmetro: ADate e retorna apenas um elemento: DailyDilbertResult, como mostrado na imagem abaixo.

WebMethod
WebMethod

Sendo assim, em nosso client SOAP, basta chamarmos esta function passando o argumento esperado e teremos o retorno desejado, como abaixo.

Coloque pra rodar e você vai ver que funciona. Ou quase. Infelizmente este webservice do Dilbert consome um site que não funciona mais e ele vai te retornar uma mensagem de erro dentro do DailyDilbertResult. 🙁

Eu demorei tanto tempo para terminar de escrever este tutorial que o site em questão mudou, hahaha. Mas não tem problema, se você receber uma mensagem de que não foi possível criar um canal seguro SSL, é porque você conseguiu se comunicar corretamente com o WebService, ele é que não conseguiu se comunicar com o site da comics.com onde ele pegava as frases do Dilbert.

Caso venha alguma coisa no objeto err, aí sim é um erro de verdade.

Eu cheguei a pensar em usar outro web service SOAP público, mas eles são raros hoje em dia e esse cumpre o papel de fazer a troca de mensagens corretamente, então fiquei com ele mesmo gerando uma única mensagem sempre. Acabei achando esse aqui depois que já tinha terminado de escrever o tutorial, então use ele como um exercício, pra ver se realmente aprendeu.

Então é isso por hoje. Tutorial curto e objetivo mas tenho certeza que vai ajudar quem estiver tendo que comunicar Node com SOAP. A título de curiosidade, não é incomum em apps mobile ter uma camada intermediária que faz esta tradução de SOAP pra REST para que o app possa trabalhar sempre apenas com REST.

Um abraço e sucesso!

Prof. Luiz

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

Como fazer upload de arquivos em Node.js

Atualizado em 21/11/2019!

Eventualmente alguém me pergunta como fazer upload de arquivos em Node.js. Não sei exatamente o motivo que me perguntam isso, uma vez que não é algo exatamente difícil, mas como é um assunto que já está na minha pauta de tanto que pedem, resolvi fazer este tutorial pra ajudar o pessoal.

Parto do princípio aqui que você já sabe o básico de Node.js, JavaScript e de HTML, conhecimentos estes que podem ser obtidos em outros artigos aqui do blog, em meu livro, em meu curso ou até mesmo no meu canal do Youtube.

Vamos começar instalando o express-generator, pacote que nos ajuda a criar aplicações web com ExpressJS, o mais famoso web framework para Node.js (talvez você precise de permissão de administrador):

Agora mande criar um novo projeto Express usando o comando abaixo, onde chamo o projeto de fileupload:

Após entrar na pasta e instalar as dependências com npm install, instale a dependência formidable, que utilizaremos para lidar com o upload de arquivos em Node.js, dentre tantas alternativas existentes na Internet.

Agora vá na pasta views e abra o arquivo index.ejs para editarmos o HTML do projeto, deixando-o como abaixo:

Note que não tem nada demais aqui, apenas um formulário com um campo de upload de arquivo. Se você executar esta aplicação com npm start verá algo como abaixo:

Formulário de Upload
Formulário de Upload

Agora vamos programar o comportamento desta página. Fazemos isso indo na pasta routes e abrindo o arquivo index.js, onde temos as rotas da tela inicial.

Adicione a nova rota abaixo dentro deste arquivo:

O que fazemos aqui? Carregamos a extensão formidable que instalamos há pouco, recebemos o form POSTado pelo usuário e salvamos o arquivo através da função form.parse. Se você testar agora já deve estar funcionando.

File Uploaded
File Uploaded

Mas…onde que o arquivo foi salvo?

Por padrão ele fica salvo em uma pasta temporária no seu computador. Para mudar isso, vamos modificar nosso código anterior para que usando a biblioteca fs (file system) do Node possamos mover o arquivo que fizemos upload para uma outra pasta da sua máquina (minha máquina é Unix, logo adapte o caminho de pastas para sua máquina):

Note que carreguei o módulo fs lá no início do código e que depois uso este módulo para mover o arquivo de lugar no sistema operacional usando a função rename. O arquivo que fiz upload é facilmente encontrado usando o argumento files, que é o terceiro da função form.parse. O resultado na tela é esse:

File Moved
File Moved

Mas no seu computador o arquivo que você fez o upload estará disponível na pasta que você mandou salvar.

Fácil, não é?!

Agora caso tenha problemas com o formidable (eu nunca tive) use a dependência multer:

E uma forma de utilizá-lo é essa:

Espero ter ajudado!

Curtiu este tutorial? Conheça o meu curso online de Node.js e MongoDB para fazer aplicações web e APIs incríveis com esta fantástica plataforma. Basta clicar no banner abaixo!

Curso Node.js e MongoDB