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

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 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. O mais próximo disso é o tutorial de Passport e as dicas que dei de usar o pacote Helmet em alguns posts.

Hoje vamos falar de JSON Web Tokens como uma forma de garantir a autenticação e autorização de uso de APIs de maneira bem simples e segura, sendo o JWT um padrão para segurança de APIs RESTful atualmente.

JSON Web Tokens

JWT, resumidamente, é uma string de caracteres codificados que, caso cliente e servidor estejam sob HTTPS, permite que somente o servidor que conhece o ‘segredo’ possa ler o conteúdo do token e assim confirmar a autenticidade do cliente.

Ou seja, quando um usuário se autentica no sistema (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
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, mas pode conter muito mais do que isso.

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.

Não focaremos no processo de autenticação inicial, que pode tranquilamente ser feito usando Passport ou diretamente com user/password batendo no servidor. Vamos mockar os dados de autenticação inicial para ir logo para a geração e posterior verificação dos tokens.

Para não termos de começar a configurar uma API do zero, recomendo baixar os fontes do tutorial de API Gateway, uma vez que o API Gateway é uma API bem simples, com apenas um arquivo index.js. Caso não queira ter de subir as APIs fake às quais ele depende, basta substituir o código de proxy por um retorno JSON fake que não vai fazer diferença nenhuma para este tutorial.

Você deve ter o seguinte index.js do API Gateway em mãos:

Com esse JS em mãos, vamos instalar algumas novas dependências na nossa aplicação que nos permitirão o uso de JWT:

A saber:

  • jsonwebtoken: pacote que implementa o protocolo JSON Web Token;
  • dotenv-safe: pacote para gerenciar facilmente variáveis de ambiente;

Vamos começar com o dotenv-safe, criando dois arquivos ocultos. 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 criptografar o token de modo que somente o servidor consiga lê-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.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

Autenticação e Autorizaçã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 emitir 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, qie 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 pedir que ele se autentique novamente.

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

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

Aqui apenas anulamos o token, embora esta rota de logout seja completamente opcional uma vez que no próprio client-side é possível destruir o cookie de autenticação e com isso o usuário está automaticamente deslogado.

Mas será que está funcionando?

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 x-access-token, 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 decodificar 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 nas chamadas GET que já existiam em nosso API Gateway:

Assim, antes de redirecionar os GETs para as APIs de destino, o API Gateway 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 /users sem estar autenticado é esse:

JWT Inexistente
JWT Inexistente

Agora, se realizarmos a autenticação usando POSTMAN:

Login via POSTMAN
Login via POSTMAN

Conseguiremos fazer novas chamadas aos endpoints da API, pois agora temos o token JWT para passar no header da nossa requisição (x-access-token):

Requisição realizada com sucesso
Requisição realizada com sucesso

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…

Token expirado
Token expirado

Bacana, não?!

E com isso encerro o artigo de hoje. Note que foquei no uso do JWT, sem entrar em muitos detalhes de como você pode estruturar sua autenticação inicial (login e senha) e nem como você pode estruturar o seu modelo de autorização (terá perfis de acesso, por exemplo?).

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

Um abraço e até mais!

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

Armadilhas e o que não é um MVP

Em meu último artigo de empreendedorismo e agilidade eu tentei ao máximo desmitificar e esclarecer de maneira clara e didática o que é um MVP. No entanto, mesmo este mindset tão poderoso pode nos fazer cair em algumas armadilhas bem comuns quando estamos construindo nossos produtos.

Duas delas eu discuti no próprio artigo anterior:

Armadilha #1: MVP é aquilo que dá pra fazer no tempo que temos;

Armadilha #2: MVP é a primeira versão do produto que tem tudo que os clientes precisam;

Ambas armadilhas podem ser solucionadas se olharmos o diagrama abaixo que mostra quais características (e qual a proporção) que nossos MVPs devem possuir:

Estrutura de um MVP
Estrutura de um MVP

Um MVP que seja apenas factível (somente feasible, armadilha #1) provavelmente não será inovador o bastante para atrair os Early Adopters (sem o fator delightful, também chamado de “fator uau”).

Um MVP que tenha 100% de todas características não é um MVP é um produto completo.

Além de falar das demais armadilhas do conceito, quero reforçar algumas coisas que não são MVPs, mas que às vezes acabam sendo confundidas erroneamente. Agora, se você não faz muita ideia do que estou falando, sugiro ler o livro Lean Startup.

Armadilha #3: Roadmap de MVPs

Um roadmap de MVPs é algo parecido com o diagrama abaixo, onde temos a evolução de um produto para cortar grama.

Roadmap de MVPs
Roadmap de MVPs

Note que o raciocínio aqui está impecável: primeira versão temos uma tesoura, pra entender se de fato os Early Adopters cortariam suas próprias gramas, segunda versão temos um cortador mais prático para as costas, até chegar no MVP 8 onde poderíamos cortar a grama de um campo de futebol facilmente.

Qual a armadilha então?

Seguir esse roadmap à risca!

O Lean Startup não possui o Lean no nome ao acaso, ele veio dessa linha de pensamento japonesa de melhoria contínua, aprendizado e redução de desperdício, assim como os Métodos Ágeis de desenvolvimento de software. Estes últimos tem como base o Manifesto Ágil, em cujo quarto item nos diz que “responder a mudanças é mais importante que seguir um plano”.

E se a grama de nossos clientes cresce tão pouco que não faz sentido comprar um equipamento de corte? E se a quantidade de grama deles é tão pequena que eles jamais precisarão de um carrinho? E se a grama deles for sintética?

Você continuaria seguindo o roadmap nestes casos?

Lembre-se que o ciclo Construir-Medir-Aprender pode nos levar ao pivot e, com isso, nosso roadmap deve ser revisto. Ou seja, a armadilha aqui é focar em seguir o roadmap desenhado ao invés de aprender com as medições.

Armadilha #4: oferecer o seu MVP pra todo mundo

Vou começar sendo bem direto: o seu MVP não é um produto para lançar para todo mundo. Isso pode soar estranho para alguns, mas é exatamente isso. E não estou falando de segmentos de mercado, mas sim de segmentar os seus potenciais clientes do MVP dentro do seu segmento de mercado, ou seja, os seus Early Adopters.

Vamos pensar rapidamente em um MVP de ponte:

MVP de Ponte
MVP de Ponte

Há quem diga que isto não é um MVP de Ponte, uma vez que não atende aos critérios do diagrama inicial deste post (temos algum “fator uau” aí?), mas se considerarmos que estas pessoas tentavam pular este rio antes da ponte ou andavam kilômetros a mais para contornar o rio, ele pode ser considerado um bem para os ribeirinhos, certo?

Qual é o mercado de uma ponte? Todo mundo que precisa atravessar um rio? Mas especificamente de um MVP de ponte construído com tábuas, será que é o mesmo mercado? Quem são os Early Adopters deste MVP de ponte?

É muito importante oferecer o MVP e em alguns casos até restringir o acesso a somente os Early Adopters que você entender que de fato vão perceber as características de seu produto como valiosas e que a falta de algumas não os impeça de pagar pelo seu uso (ok, na ponte não há cobrança, mas você entendeu a analogia).

O que acontece se eu não restringir o acesso ao meu MVP a somente os Early Adopters? Várias coisas:

  • em uma análise quantitativa, o número de clientes que não gostaram do produto pode ofuscar o número de early adopters que de fato gostaram e que lhe dariam um feedback valioso (aprendizado); quem você quer agradar com a ponte de tábua, os ribeirinhos desesperados por uma solução rápida ou os turistas da cidade buscando uma experiência inesquecível?
  • pode queimar sua imagem no mercado com um “produto ruim” (nem todo cliente tem o mesmo mindset de um Early Adopter), uma vez que sua empresa já possua uma marca a zelar; imagine uma construtora inaugurando aquele MVP e ponte…
  • pode lhe trazer sérios problemas legais e de segurança caso clientes tenham experiências péssimas com um produto que pagaram para usar; imagine seu MVP de ponte sendo usado por um carro…

Armadilha #5: não definir seus Early Adopters

A quinta armadilha está bem interligada com a quarta, mas vou ser redundante mesmo assim: o seu MVP é para os seus Early Adopters. Se você não sabe quem são os seus Early Adopters, você nem deveria começar seu MVP.

O gráfico abaixo mostra a adoção de novas tecnologias pela população:

Adoção de novas tecnologias
Adoção de novas tecnologias

Dentro dos segmentos de mercado temos sempre aquelas pessoas que são mais propensas a adotar novas tecnologias e aquelas que só vão fazê-lo em último caso. Tipo meu sogro que ainda usava telefones de flip (estilo clamshell, de “abrir”) até pouco tempo e só trocou por um touch pois o dele estragou e não vendem mais o outro modelo.

Os Early Adopters são uma fatia do seu mercado, algo menor que 25%, que gostam de ser os primeiros a testar um novo produto, a usar uma nova funcionalidade, sentem-se valorizados quando as marcas pedem seu feedback e são críticos de maneira construtiva, colaborando com a evolução de produtos. Eles gostam de entrar em filas de espera de devices, de testar demos de jogos, se orgulham de usar softwares antes dos outros, mesmo que isso tudo incorra em estar sujeito a falhas ocasionais, falta de funcionalidades, etc.

Você sabia que o primeiro iPhone tinha a pior qualidade de ligação do mercado? E que ele não permitia copiar e colar texto entre apps? E que a Apple poderia ter lançado ele melhor do que lançou, mas que queria justamente testar o mercado? Isso não o impediu de ser um sucesso de vendas entre os early adopters da empresa.

Não definir antecipadamente quem são os seus Earl Adopters, e restringir o acesso ao seu MVP para eles pode gerar problemas enormes, principalmente se seu produto for um MVP de ponte…

MVP de Ponte - fail
MVP de Ponte – fail

O que não é um MVP?

Muitas vezes quando queremos que as pessoas entendam um conceito, precisamos mostrar a elas o que não é aquele conceito.

Um MVP não é um protótipo ou prova de conceito, não ao menos no sentido literal que estas palavras possuem dentro do ramo científico. Um MVP é um produto comercializável, enquanto que uma prova de conceito pode ser algo bem longe de ser comercializável. Cada um tem a sua utilidade, apenas não são a mesma coisa.

Um MVP não é o beta de um produto. Entende-se que um beta é um produto quase finalizado que abrimos para um grupo fechado de pessoas usarem e nos darem seu feedback, bem como descobrirmos erros ocultos que só os usuários conseguem detectar. Um MVP, principalmente nas iterações iniciais, é algo bem longe de um produto completo.

Um MVP não é a versão 1 de um produto. Ele está mais para uma v0.1. Se você chama suas v1.0 de MVP ou você está criando MVPs grandes demais, ou sua v1.0 é muito pequena. De qualquer maneira você está errado.

Um MVP não é um produto de mercado. Ele é um produto de nicho, para seus Early Adopters.

E finalmente, um MVP é um Minimum Viable Product e não um Maximum Viable Product.

Espero ter sido claro! 🙂

Quer ter ideias de como criar um MVP na prática? Dê uma olhada neste artigo aqui.

Querendo aprender a metodologia correta de desenvolvimento de software iterativo-incremental para não correr o risco de cair no modelo Waterfall de desenvolvimento de produtos? Conheça meu livro Scrum e Métodos Ágeis!

API Gateway em Arquitetura de Microservices com Node.js

Recentemente escrevi uma série de artigos sobre arquitetura de microservices usando Node.js e MongoDB. Na situação atual desta série temos alguns microservices criados e, com isso, uma complexidade que não existia nos webservices monolíticos de antigamente: múltiplos endpoints de múltiplos serviços para uma única aplicação.

Será que o(s) client(s) deveriam conhecer as especificidades de cada microservice? E no caso de algoritmos que são padrões a todos os microservices (como autenticação e autorização), devem ser repetidas em todos microservices?

Como resolver estas e outras questões sem gambiarra, de maneira corporativa? Usando um API Gateway!

Você verá neste artigo:

  1. API Gateway e API Manager
  2. O que vamos fazer
  3. Criando a API gateway
  4. Testando e indo além!

Se quiser ver este mesmo tutorial em formato de vídeo aula, confira o meu Curso de Node.js e MongoDB!

#1 – API Gateway e API Manager

Resumidamente um API Gateway fornece um ponto de acesso único à sua arquitetura de microservices. Não importa quantos microservices você tenha, colocando um API Gateway à frente deles você terá uma única URL para se preocupar. O API Gateway por sua vez roteia e gerencia o tráfego de requisições para os microservices de destino.

Já um API Manager é um API Gateway mais ‘bombado’, que além de atuar como proxy realiza toda uma governança das chamadas realizadas aos microservices, como analytics, versionamento de APIs, caching, dashboards, segurança, transformação de dados, agregação de dados, etc.

Uma abordagem possível é ter um API Gateway por diferente client, como na imagem abaixo:

API Gateway por client
API Gateway por client

Uma solução bem comum no mercado é usar um proxy reverso como nginx, mas Node.js também é um excelente opção para construção de API Gateways quando se deseja um controle maior das requisições, permitindo fazer algo mais próximo de um API Manager sob medida. É exatamente o que fez a Netflix, que construiu um API Gateway em Node.js para ficar na frente das suas centenas de APIs Java, como mostra o diagrama abaixo:

Arquitetura Netflix
Arquitetura Netflix

Especialmente para clientes mobile, que possuem severas limitações de latência e qualidade de conexão à Internet, um API Gateway pode simplificar em uma única chamada mobile, diversas requisições aos microservices que, acontecendo na rede local do datacenter, serão muito mais velozes do que se o dispositivo móvel tivesse de fazer diversas chamadas.

A diferença entre API Gateway e API Manager é tênue, uma vez que há diversos fabricantes e nomenclaturas diferentes no mercado. O termo mais comumente utilizado é de API Gateway, geralmente sendo chamados de API Managers as soluções comerciais corporativas.

#2 – O que vamos fazer

A ideia deste tutorial é fazer um API Gateway simples em Node.js, seguindo a arquitetura abaixo:

API Gateway Simples
API Gateway Simples

Nesta arquitetura, o API Gateway recebe e roteia as requisições para microservices dentro de uma DMZ (Zona Desmilitarizada ou Zona de Perímetro), um conceito de segurança da informação para proteger a rede da empresa dos serviços e vice-versa.

Assim, nosso cliente somente conhecerá uma API, que é a API Gateway, podendo existir muitos outros microservices por trás dele. Essa abordagem é especialmente interessante quando estamos refatorando serviços monolíticos para microservices: podemos substituir as chamadas ao serviço antigo para chamadas ao API Gateway e roteamos para o serviço antigo. Conforme o time for ‘escamando’ o serviço antigo em microservices, vamos ajustando no API Gateway para chamar ora o serviço antigo, ora os novos microservices. O diagrama abaixo mostra isso:

API Gateway e Design Evolucionário
API Gateway e Design Evolucionário

Mais pra frente podemos aproveitar esta centralização das requisições para adicionar segurança à todas as chamadas como autenticação e rate limit (controle de uso das APIs, para evitar exageros).

Para executar este tutorial na prática é importante que você tenha alguns microservices prontos na sua máquina, no mínimo dois. Se você não tiver, nos fontes deste tutorial eu inclui dois micro serviços fake que sempre retornam os mesmos dados mockados, para que você possa testar. No entanto, o mais recomendado é que você realize os tutoriais de microservices que estão aqui no blog para realmente aprender o tópico completo.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

#3 – Criando a API Gateway

 

Por incrível que pareça, criar o esqueleto de um API Gateway é muito simples.

Vamos começar criando uma pasta para o nosso projeto, chamado api-gateway. Dentro dessa pasta crie um arquivo index.js e via terminal, navegue até a pasta do api-gateway e rode o comando ‘npm init’ para inicializar este projeto Node.js.

Configure a sua aplicação através do assistente do npm init para que ele crie o package.json. Agora que temos um package.json, via terminal vamos mandar instalar as dependências que vamos precisar em nosso projeto:

Esse comando vai instalar as seguintes dependências:

  • express: nosso pacote para criar web APIs e web apps facilmente;
  • morgan: o logger que usaremos para saber o que está acontecendo com nosso API Gateway no terminal;
  • helmet: pacote que adiciona proteção a 11 tipos comuns de requisições maliciosas, adicionando alguma segurança ao API Gateway;
  • express-http-proxy: pacote para redirecionar requisições para os microservices;
  • http: módulo básico para criar um servidor HTTP básico;
  • cookie-parser: módulo que não usaremos agora, mas que no futuro será útil para fazer o parsing dos cookies das requisições;

O código abaixo mostra o que é possível fazer em poucas linhas:

Para quem já criou uma API em Express antes não há nada excepcionalmente novo aqui. Eu configurei os pacotes nas primeiras linhas, usei o express-http-proxy para criar proxies de chamadas para dois serviços fake (disponíveis nos fontes no final deste artigo), um de users e outro de products.

A parte de mapeamento das URLs é o mesmo padrão do Express, onde eu redireciono as chamadas /users para o proxy de users e as chamadas de /products para o proxy de products. e no final as configurações do app Express para usar os módulos de logging, segurança, os encodings e o cookie-parser, exatamente nesta ordem.

As duas últimas linhas criam o servidor HTTP para que ele passe a escutar na porta 3000. Note que você terá de subir as suas APIs em outras portas ou até mesmo em outras URLs, deixando a API Gateway como rota default para as chamadas do seu cliente.

#4 – Testando e indo além

Para subir as APIs fake, rode um ‘npm install’ nelas primeiro para instalar as dependências e depois um ‘npm start’ em cada uma, sendo que users está configurada pra rodar na porta 3001, e products na 3002.

Depois que estiver com as duas APIs fake rodando, experimente testá-las no navegador para ver que elas apenas listam sempre os mesmos dados mockados.

Depois, suba o seu api-gateway mandando executar o index.js. Teste o api-gateway diretamente no navegador fazendo as mesmas chamadas que faria aos microservices originais. Se tudo ocorreu bem, o retorno será o mesmo que nos microservices originais, sem qualquer diferença aparente, de forma transparente para o cliente.

E justamente essa é a grande vantagem de se trabalhar com um API Gateway!

Como o Express funciona como um middleware de requisições, é muito simples e prático adicionar camadas adicionais de execução antes de repassar a requisição aos microservices (como segurança com JWT), permitindo todo tipo de gerenciamento de APIs que você imaginar.

Abordagens mais profissionais vão ter o mapeamento parametrizável através de banco de dados para que seja simples de adicionar novas APIs e rotas ao Gateway sem ter de mexer em programação.

De qualquer forma espero que este artigo tenha lhe dado uma ideia de como fazer essa camada importantíssima na arquitetura de microservices.

Muito deste tutorial foi baseado no excelente material da Rising Stack, incluindo os diagramas.

Até a próxima!

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