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

Tutorial de validação de Input de dados em Node.js

Este tutorial requer conhecimento prévio de Node.js, que podem ser obtidos em outros artigos, em meus livros ou meu curso online, onde inclusive possui uma aula em vídeo ensinando a fazer a mesma coisa.

Validar o input/entrada de dados em aplicações é uma das poucas verdades absolutas no mundo da programação. Você simplesmente não pode, jamais, confiar que os dados enviados para sua aplicação processar estarão no formato e tipo corretos que você precisa.

Mesmo que você adicione validação na sua interface ou front-end, dificilmente você conseguirá garantir que, em algum momento, algum usuário descuidado ou malicioso acabe lhe enviando dados fora do padrão que você espera e isso pode ser desastroso.

Quando você pensa em desenvolvimento web então, nem se fala. Existem muitas formas de burlar validações colocadas no front-end, seja através de maneiras simples como desativando javascript ou inspecionando e alterando o HTML da página e muito mais, a formas mais complexas como capturando requisições HTTP do front para o backend e alterando as mesmas (Man in the Middle Attack).

No caso do Node.js, mais especificamente do web framework Express, o mais usado nesta plataforma, os dados recebidos são provenientes dos objetos body, param e query, que aceitam apenas texto de qualquer formato. Se você está fazendo web APIs RESTful, esta validação que vou ensinar neste tutorial será a sua primeira e muitas vezes única defesa contra o descuido e as más intenções de quem for utilizar as suas APIs.

Embora você possa fazer todas suas validações manualmente, usando ‘ifs’ e outras práticas básicas de programação, isso geralmente leva a muito código repetitivo, complexo de testar unitariamente e arquiteturas pouco elegantes. Usaremos neste tutorial o pacote @hapi/joi, o mais popular atualmente para validação de dados e espero gerar alguns insights de como você pode levar validação a sério na sua aplicação sem torná-la um espaguete Javascript.

Então vamos lá!

Curso Node.js e MongoDB

Vamos começar de maneira bem simples, criando um novo projeto Node.js para mostrar o básico do @hapi/joi e mais tarde espero poder trazer conceitos mais avançados em artigos futuros.

Crie uma pasta chamada node-validation no seu computador e navegue até ela via terminal para criar um projeto dentro dela, usando o npm init. Crie também uma index.js aí.

Adicione no projeto a extensão que vamos usar, com o comando abaixo:

O @hapi/joi (evolução do antigo pacote Joi) usa uma linguagem descritiva para criar schemas de validação (ao contrário de estruturas imperativas como ifs) e é um desses schemas que vamos criar no início do seu index.js, usando o código abaixo de exemplo, que será explicado a seguir.

Na primeira linha, apenas carregamos uma constante ‘Joi’ que será utilizada para criar nossos schemas de validação (através de Joi.object) e o primeiro deles será o userSchema, que possuirá todas as regras de validação de usuários cadastrados no sistema.

A primeira regra é relacionada ao username, que será uma string (Joi.string), alfanumérica (alphanum), com no mínimo 3 no máximo 30 caracteres (min e max) e que é uma propriedade obrigatória (required).

A segunda regra é no password, que também deve ser uma string mas deve seguir o padrão (pattern) declarado sob a forma de uma expressão regular que aceita de 3 a 30 letras maiúsculas, minúsculas e números.

Já a terceira regra, birth_year define que esse dado deve ser um número (Joi.number), inteiro (integer) e com o seu valor entre 1900 e 2001 (min e max).

A última regra, email, foi colocada com o intuito de mostrar o poder desta biblioteca que possui muitas funções utilitárias para facilitar a sua vida, como as já mostradas anteriormente (string, number, required, etc) e aqui temos a função email que recebe configurações por parâmetro como minDomainSegments (para definir o número de partes mínima no domínio do e-mail) e tlds (Top Level Domains – para permitir ou bloquear extensões específicas de domínio).

Agora, para usar este schema de validação é muito simples, basta chamar o objeto userSchema passando por parâmetro na function validate o objeto a ser validado, sendo importante que os nomes das propriedades coincidam.

No primeiro teste, o val1 virá com o erro vazio, pois o objeto passado por parâmetro atende aos requisitos do userSchema. Já no segundo teste, val2 terá um erro pois não foi passado o username, que é obrigatório segundo as regras que criamos (required).

Rode este projeto simples no terminal e você verá essas validações em ação.

Resultado das Validações
Resultado das Validações

Antes de prosseguir, sugiro que “desafie” as validações do Joi com outros testes que cubram mais cenários possíveis, só para ver que de fato ele funciona como deveria.

Agora que entendemos o básico, é importante que a gente separe a lógica de validação da lógica de aplicação.

Para fazer isso, crie outro arquivo na sua aplicação que chamaremos de validations.js, com o seguinte conteúdo:

Note que eu extraí a lógica de validação do index.js para o validations.js e apenas adicionei um module.exports no final, para expor o schema para fora deste módulo JS.

Agora o nosso index.js ficará bem menor, uma vez que a aplicação não faz nada exceto chamar as validações pré-configuradas.

Rode novamente esta aplicação no terminal e ela deve continuar funciona como anteriormente, mas agora com uma organização mais elegante!

E por hoje é só pessoal. Espero em próximos artigos explorar formas mais elaboradas de validação, incluindo usando middlewares do Express em aplicações web, mas tenho certeza que esta introdução já lhe deu algumas ideias de como fazê-lo.

Um abraço e sucesso!

Prof. Luiz

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