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

Node.js

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

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

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

Faz algum tempo que ensino aqui no blog a como fazer APIs em Node.js, uma vez que este é o cenário mais comum de uso com a plataforma.

Já ensinei como o HTTP funciona e a fazer APIs com MongoDB, MySQL e SQL Server. Já ensinei a como estruturar suas APIs em formato de micro serviços e como criar um API Gateway.

No entanto, pouco falei aqui sobre segurança em APIs, então hoje vamos falar de JSON Web Tokens como uma forma de garantir a autenticação e autorização de APIs de maneira bem simples e segura, sendo o JWT um padrão para segurança de APIs RESTful atualmente.

Veremos neste tutorial:

  1. JSON Web Tokens
  2. Estruturando a API
  3. Adicionando o JWT
  4. Autenticação
  5. Autorização

Se preferir, pode assistir ao vídeo abaixo ao invés de ler o artigo.

Vamos lá!

Curso FullStack

#1 – JSON Web Tokens

JWT, resumidamente, é uma string de caracteres que, caso cliente e servidor estejam sob HTTPS, permite que somente o servidor que conhece o ‘segredo’ possa validar o conteúdo do token e assim confirmar a autenticidade do cliente. O token não é “criptografado”, mas “assinado”, de forma que só com o secret essa assinatura possa ser comprovada, o que impede que atacantes “criem” tokens por conta própria.

Em termos práticos, quando um usuário se autentica no sistema ou web API (com usuário e senha), o servidor gera um token com data de expiração pra ele. Durante as requisições seguintes do cliente, o JWT é enviado no cabeçalho da requisição e, caso esteja válido, a API irá permitir acesso aos recursos solicitados, sem a necessidade de se autenticar novamente.

O diagrama abaixo mostra este fluxo, passo-a-passo:

Fluxo JWT

O conteúdo do JWT é um payload JSON que pode conter a informação que você desejar, que lhe permita mais tarde conceder autorização a determinados recursos para determinados usuários. Minimamente ele terá o ID do usuário autenticado ou da sessão (se estiver trabalhando com este conceito), mas pode conter muito mais do que isso, conforme a sua necessidade, embora guardar conteúdos “sensíveis” no seu interior não é uma boa ideia, pois como disse antes, ele não é criptografado.

#2 – Estruturando a API

Antes de começarmos esta API Node.js usando JWT vale ressaltar que o foco aqui é mostrar o funcionamento do JWT e não o funcionamento de uma API real. Caso você já possua uma web API, pule esta seção. Caso contrário, use a API fake abaixo, que vamos mockar os dados retornados pela API e as credenciais de autenticação inicial para ir logo para a geração e posterior verificação dos tokens.

Salve o seguinte código JavaScript em um arquivo /jwt/index.js:

Com esse JS em mãos, vamos instalar algumas dependências na nossa aplicação para fazê-la funcionar:

Rode a API com “node index” e ao acessar localhost:3000 deve listar apenas uma mensagem de OK e ao acessar o caminho /clientes no navegador, deve listar um array JSON como abaixo.

API fake funcionando

Isso mostra que a API está funcionando em ambas as rotas e sem segurança, afinal, não tivemos de nos autenticar para fazer os GETs que fizemos.

#3 – Adicionando o JWT

Agora, vamos instalar duas novas dependências para incrementar o nosso projeto, permitindo adicionar autenticação via JWT:

A saber:

  • jsonwebtoken: pacote que implementa o protocolo JSON Web Token;
  • dotenv-safe: pacote para gerenciar facilmente variáveis de ambiente, não é obrigatório para JWT, mas uma boa prática para configurações em geral;

Vamos começar usando o dotenv-safe, criando dois arquivos. Primeiro, o arquivo .env.example, com o template de variáveis de ambiente que vamos precisar:

E depois, o arquivo .env, com o valor do segredo à sua escolha:

Este segredo será utilizado pela biblioteca jsonwebtoken para assinar o token de modo que somente o servidor consiga validá-lo, então é de bom tom que seja um segredo forte.

Para que esse arquivo de variáveis de ambiente seja carregado assim que a aplicação iniciar, adicione a seguinte linha logo no início do arquivo index.js da sua API, aproveitando para inserir também as linhas dos novos pacotes que vamos trabalhar:

Isso deixa nossa API minimamente preparada para de fato lidar com a autenticação e autorização.

#4 – Autenticação

Caso você não saiba a diferença, autenticação é você provar que você é você mesmo. Já autorização é você provar que possui permissão para fazer ou ver o que você está tentando.

Antes de gerar o JWT é necessário que o usuário passe por uma autenticação tradicional, geralmente com usuário e senha. Essa informação fornecida é validada junto a uma base de dados e somente caso ela esteja ok é que geramos o JWT para ele.

Assim, vamos criar uma nova rota /login que vai receber um usuário e senha hipotético e, caso esteja ok, retornará um JWT para o cliente:

Aqui temos o seguinte cenário: o cliente posta na URL /login um user e um pwd, que simulo uma ida ao banco meramente verificando se user é igual a luiz e se pwd é igual a 123. Estando ok, o banco me retornaria o ID deste usuário, que simulei com uma constante.

Esse ID está sendo usado como payload do JWT que está sendo assinado, mas poderia ter mais informações conforme a sua necessidade. Além do payload, é passado o SECRET, que está armazenado em uma variável de ambiente como mandam as boas práticas de segurança. Por fim, adicionei uma expiração de 5 minutos para este token, o que quer dizer que o usuário autenticado poderá fazer suas requisições por 5 minutos antes do sistema ou Web API pedir que ele se autentique novamente.

Caso o user e password não coincidam, será devolvido um erro ao usuário.

Vamos aproveitar o embalo e vamos criar uma rota para o logout:

Aqui apenas dizemos ao requisitante para anular o token, embora esta rota de logout seja completamente opcional uma vez que no próprio client-side é possível destruir o cookie ou localstorage de autenticação e com isso o usuário está automaticamente deslogado. Se nem o client-side ou o server-side destrua o token, ele irá expirar sozinho em 5 minutos.

Se quiser adicionar uma camada a mais de segurança, você pode ter uma blacklist de tokens que fizeram logout, para impedir reuso deles depois do logout realizado. Apenas lembre-se de limpar essa lista depois que eles expirarem ou use um cache em Redis com expiração automática.

Mas será que está funcionando?

#5 – Autorização

Aí que entra a autorização!

Vamos criar uma função de verificação em nosso index.js, com o intuito de, dada uma requisição que está chegando, a gente verifica se ela possui um JWT válido:

Aqui eu obtive o token a partir do cabeçalho authorization, que se não existir já gera um erro logo de primeira.

Caso exista, verificamos a autenticidade desse token usando a função verify, usando a variável de ambiente com o SECRET. Caso ele não consiga verificar o token, irá gerar um erro. Em seguida chamamos a função next que passa para o próximo estágio de execução das funções no pipeline do middleware do Express, mas não antes de salvar a informação do id do usuário para a requisição, visando poder ser utilizado pelo próximo estágio.

Ok, entendi que esta função atuará como um middleware, mas como usaremos a mesma?

Basta inserirmos sua referência na chamada GET /clientes que já existia em nossa API:

Assim, antes de responder os GETs de clientes, a API vai criar essa camada intermediária de autorização baseada em JWT, que obviamente vai bloquear requisições que não estejam autenticada e autorizadas, conforme as suas regras para tal.

O resultado, tentando chamar a rota /clientes sem estar autenticado é esse:

JWT não informado

Note que eu não disse para colocar esse middleware de segurança na chamada GET / pois vamos deixar ela pública, sempre acessível mesmo a usuários anônimos.

Mas voltando à nossa rota de clientes, para que seja possível acessá-la o client da API deve primeiro obter um token se autenticando com um usuário válido na rota POST de login. Essa requisição teremos de realizar usando POSTMAN, Insomnia ou similar (cURL,etc):

Login via POSTMAN

Na aplicação que for consumir a sua web API, deverá ser coletado esse token gerado e todas as requisições aos endpoints protegidos devem ser feitas passando o mesmo no header da requisição. Para simular isso, usaremos o POSTMAN novamente, passando o conteúdo do JWT no header Authorization (aba Headers do Postman). E por fim, como este token está programado para expirar 5 minutos após a sua criação, as requisições podem ser feitas usando o mesmo durante este tempo, sem novo login. Mas assim que ele expirar, receberemos como retorno um erro.

Bacana, não?!

E com isso encerro o tutorial de hoje. Note que foquei no uso do JWT, sem entrar em muitos detalhes de como você pode estruturar sua autenticação a partir do banco de dados (tabela/coleção login e senha) e nem como você pode estruturar o seu modelo de autorização (perfis de acesso, por exemplo).

Quem sabe não abordo este tema de maneira mais avançada e completa em outra oportunidade?

Por ora, abordei uma maneira ainda mais segura de ter seu JWT neste artigo, caso esteja trabalhando com microservices e compartilhando token entre eles, mas não queira compartilhar secrets.

Se quiser conhecer outras formas de deixar suas Web APIs seguras, lei este artigo aqui.

E, para mais uma dica de segurança com Node.js, assista ao vídeo abaixo:

Um abraço e até mais!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu curso de 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 *

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

Thiago Peluque

Sei que é um post de 3 anos atrás porém tentei seguir e quando ele vai validar o Token, ele apresenta um erro

(node:40877) UnhandledPromiseRejectionWarning: #
(node:40877) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)

Já tentei de todas as formas validar isso porém sem sucesso. Ele apresenta isso bem no next()

Alguém tem idéia do que seria??

Luiz Duarte

Esse erro indica que uma função assíncrona no seu projeto disparou um erro não tratado. Experimente usar try/catch para capturar os erros ou pelo menos use um console.log para tentar descobrir em que parte da chamada ele deu o erro.

Você citou que seria no next, então dá uma olhada em qual seria a próxima função de middleware que foi chamada pois é nela o problema. Outra dica é dar uma olhada nos fontes da lição no GitHub, para ver se não deixou escapar nada, formulário ao final do post.

Zoltan Caputo

Excelente professor! Vários tutoriais sensacionais. Gostaria de ter conhecido antes de ter feito outros bons cursos. Vale demais iniciante começar com Luiztools…

Luiz Duarte

Fico feliz que tenha gostado dos conteúdos Zoltan!