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

Atualizado em 10/02/2020 para facilitar o entendimento.

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.

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!

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

Autenticação em Node.js com Passport

Atualizado em 24/01/2020! Caso queira ver este artigo em formato de vídeoaula, adquira o meu curso de Node.js e MongoDB.

Recentemente escrevi um tutorial onde ensino como fazer uma aplicação de chat usando Node.js e Socket.io. Na última parte do tutorial, dou uma série de ideias de coisas que podem e/ou devem ser feitas para deixar a solução mais profissional, independente se você está apenas estudando ou trabalhando com a tecnologia. Em outros tutoriais, também ensinei a fazer CRUDs completos usando Express e diversos bancos de dados, relacionais (como MySQL e SQL Server) e não-relacionais.

Ou quase completos, afinal, eu sempre pulo a parte da autenticação.

Verdade seja dita, é difícil um sistema web que não tenha algum tipo de identificação, mesmo que você não veja como uma medida de segurança em si. A Internet é uma espécie de terra sem lei, e mesmo em serviços gratuitos, como os do Google, a autenticação garante que abusos serão evitados ou ao menos controlados.

Dito isso, hoje veremos como fazer autenticação de aplicações e APIs web com Node.js usando Passport!

  1. A aplicação web
  2. Escolhendo a tecnologia de autenticação
  3. Configurando o banco de dados e variáveis de ambiente
  4. Programando sua estratégia de autenticação
  5. Configurando o projeto para autenticação
  6. Fazendo o login funcionar

Este e outros tutoriais incríveis podem ser vistos em videoaulas no meu curso de Node.js e MongoDB.

#1 – A aplicação web

Minha sugestão: faça o tutorial de como criar um chat usando Node.js e Socket.io primeiro, pois partirei dele aqui. Você também pode usar os ensinamentos deste post em outra aplicação, as mudanças são bem sutis, vou sinalizar elas no texto. E você pode também baixar os fontes do referido tutorial para poder acompanhar.

Primeiro passo de preparação da nossa aplicação para ter autenticação: criar uma tela de login!

Crie na sua aplicação uma nova view chamada login.ejs com o seguinte HTML dentro:

Note que deixei um código de servidor verificando a existência de uma message no model, para renderizar uma label de erro ali embaixo. Usaremos isso mais tarde para avisar de erros de autenticação. Também deixei dois links, um para recuperação de senha e outro para cadastro, sendo que mais tarde criaremos estas duas telas e seus funcionamentos.

E na sua rota index.js, modifique a rota raiz para renderizar esta view, afinal, não estávamos usando aquela página inicial para nada mesmo!

Com isso, ao entrar na aplicação de chat, obrigatoriamente o usuário verá primeiro a tela de autenticação. Obviamente isso não inibe ninguém de acessar páginas internas através da URL do navegador, mas um problema de cada vez, por favor!

Tela de Login
Tela de Login

Programaremos o seu funcionamento mais pra frente.

#2 – Escolhendo a tecnologia de autenticação

Neste tutorial usarei o módulo Passport, que é o padrão quando o assunto é autenticação em Express, o webframework que é padrão quando o assunto são webapps em Node.js, a plataforma que é padrão quando o assunto… (e assim por diante). No entanto, Passport está longe de ser uma bala de prata quando o assunto é segurança nas suas aplicações. Passport é apenas um middleware de segurança que exige que os desenvolvedores saibam o que estão fazendo e que programem a segurança do jeito que quiserem, sendo muito flexível, extensível e de uso comum no mercado.

Neste tutorial, tentarei ser o mais profissional possível em termos de segurança considerando que muitos leitores costumam usar os códigos que forneço/ensino em seus sistemas, no entanto, vale a ressalva de sempre ser crítico e cético quanto a códigos da Internet, especialmente aqueles que podem danificar o seu sistema como a camada de segurança.

Outras opções de autenticação existem aos montes na Internet, principalmente as baseadas em padrões web e através de APIs, o que lhe eximem muitas vezes a responsabilidade de uma série de tarefas burocráticas de se ter autenticação em sua aplicação, como autenticar, armazenar senhas, cadastro de usuários, recuperação de senhas, etc. Nomes como Google Account, Microsoft Account, Facebook Authentication, Auth0 e até mesmo Firebase possuem soluções para isso, sem contar Github, Twitter e a lista vai longe. São todas excelentes opções mas que eventualmente te engessam em algumas questões e te torna dependente das empresas em questão.

Sendo assim, optarei aqui pelo Passport não por ele ser a melhor solução de autenticação do mercado, mas por ser aberto, extensível, flexível, etc. E por ter uma boa adesão do mercado quando o assunto é Node.js.

#3 – Configurando o banco de dados e variáveis de ambiente

Os dados de sessão dos usuários vão ficar armazenados em um banco MongoDB (embora também seja possível usar Redis como alguns tutoriais ensinam por aí). Eu já expliquei tudo que você precisa saber sobre como configurar o ambiente MongoDB pra uso com Node neste tutorial e caso não esteja com tempo agora, sugiro apenas que crie uma conta na Umbler, crie um site Node.js e use o serviço de MongoDB deles usando os créditos que você ganha quando se cadastra.

Voltando ao nosso projeto Node.js, instale o driver do MongoDB para Node:

Agora instale o módulo dotenv-safe, que permitirá guardarmos variáveis de ambiente de uma maneira muito prática e profissional, permitindo fazer deploy do nosso projeto em produção sem ter de ficar trocando connection strings e coisas do tipo:

Pra que o dotenv-safe funcione (ensino em videoaulas do meu curso de Node.js e MongoDB), você precisa criar dois arquivos: “.env” e “.env.example”. O “.env.example” é o template contendo quais variáveis de ambiente são obrigatórias para sua aplicação funcionar, em nosso caso, apenas duas:

Já o “.env”, contém a sua versão local das variáveis e o ideal é que você inclua no seu .gitignore o .env, para que ele não seja commitado em produção (em produção você deve setar as variáveis de ambiente de produção, e não vai querer elas sobrescritas de maneira alguma):

Agora modifique o seu bin/www (geramos este projeto com o express-generator, lembra?) para que o servidor somente seja iniciado após a conexão com o banco de dados tiver sido realizada, como abaixo (obviamente informe os dados da sua conexão com o banco), onde uso a variável de ambiente como connection string:

O objeto db é a conexão com o banco e vamos compartilhá-lo globalmente com os outros módulos que precisarem (apontando diretamente para o banco configurado na variável de ambiente). Caso o .env não exista, teremos um erro. Caso não consiga se conectar ao banco, teremos um erro. O projeto somente vai “subir” se estiver tudo 100%.

Se você rodar este projeto agora e tudo estiver 100%, você deve ver a mensagem no console de que a conexão foi bem sucedida.

Antes de prosseguirmos, é de bom tom cadastrarmos no mínimo um usuário em nosso banco de dados, para poder realizar os testes depois. Acesse o seu banco MongoDB local ou remoto e insira na coleção users um usuário com os seguintes dados:

Coloque no campo email, um email seu de verdade, pois precisaremos dele funcionando mais tarde. Note que o password do usuário está criptografado. Esta senha na verdade é uma versão criptografada da palavra “123”. 🙂

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

#4 – Programando sua estratégia de autenticação

Agora que temos o banco de dados funcionando podemos definir a nossa estratégia de autenticação. Vamos fazer isso em um novo arquivo chamado auth.js na raiz do nosso projeto.

Mas antes de mexermos neste arquivo, vamos instalar e mexer em duas dependências que precisaremos.

Lembra quando falei que o Passport era bacana por ser extensível? A estratégia de autenticação é completamente configurável, desde os padrões de terceiros, até padrões abertos como OAuth ou o clássico username/password. Aqui vamos instalar o módulo passport-local, que define como estratégia o uso de usuário e senha armazenados em nosso próprio banco para funcionar (clássico username/password):

E vamos também instalar o módulo bcrypt para manter nossas senhas seguras no banco de dados. Armazenar hashs das senhas ao invés de texto plano no banco de dados é uma política mínima visando segurança das informações.

Atenção: caso o módulo acima falhe na instalação, use bcryptjs no lugar. Neste caso o seu require será em cima de bcryptjs, mas os demais códigos deste tutorial funcionam normalmente.

Agora sim vamos voltar ao nosso auth.js e carregar estes dois módulos, além de definir o nosso module.exports que vai configurar (em uma espécie de inversão de controle) o objeto do passport (que é o middleware de autenticação) que criaremos mais pra frente:

Note o comentário ali dentro da function exportada: é ali que vamos trabalhar agora.

Primeiro, crie ali dentro duas functions de busca de usuário em nossa base Mongo. Precisamos das duas para fazer nossa autenticação funcionar, você já vai ver porque:

A primeira função busca um usuário por username e passa o retorno para uma função de callback. Já a segunda busca por id e também passa o retorno por callback.

Por questões de segurança e praticidade ao mesmo tempo, o cookie de autenticação (que falarei melhor mais tarde) não conterá as informações da sessão, as mesmas ficarão no banco e teremos apenas o id localmente nos clientes. Assim, precisamos configurar as funções de serialização e desserialização de usuário ainda dentro daquele espaço de exportação desse módulo:

A função done é “nativa” do passport e serve para sinalizar erro e passar informações do usuário para o passport. Na função serializeUser, passamos a chave primária (ObjectId do MongoDB) para ser serializado no cookie do usuário. Já no deserializeUser, recebemos o id e vamos com ele no banco de dados retornar o usuário inteiro.

E por fim, vamos definir a estratégia de autenticação em si, chamada de LocalStrategy, ainda no final do espaço do module.exports:

Vamos por partes aqui pois é bastante coisa:

  • usernameField e passwordField são os names dos inputs no form HTML que será postado durante a autenticação;
  • a function findUser é a que criamos anteriormente que vai no MongoDB buscar usuário por username;
  • se der erro ou se não existir aquele username, avisamos ao passport que não rola essa autenticação através da função done;
  • se o usuário existir, usaremos o bcrypt.compare entre a senha digitada no form de login (password) e a senha salva (hash) no banco de dados;
  • o retorno dessa comparação também é sinalizado ao passport via function done.

Com isso finalizamos nosso módulo com a estratégia de autenticação. Uma versão mais parruda desse módulo poderia contabilizar o número de tentativas erradas para pedir que o usuário solucione um Captcha, bloquear o usuário, avisar o usuário quando um login de novo IP acontecer, etc.

#5 – Configurando o projeto para autenticação

Basicamente quando um usuário entra na página inicial do seu webapp, ele não está autenticado, está iniciando uma sessão anônima. Para que ele possa entrar nas páginas seguras do seu webapp, ele deve estar autenticado, em uma sessão autenticada. Essa autenticação ocorre durante o processo de login, onde usando credenciais válidas (usuário e senha, por exemplo), a sessão do nosso usuário recebe um cookie identificando-o. Nos demais acessos, esse cookie é verificado para conceder ou não autorização para navegar entre as páginas.

O uso de cookies e sessões é o jeito mais comum de garantir que um usuário está autenticado em páginas web e é o que usaremos aqui. Antes de mexer com eles, vamos adicionar o módulo passport em nosso projeto, com o comando abaixo (curioso como já falamos bastante nele mas só agora adicionamos no projeto):

Para manipular a sessão de aplicações web feitas usando Express usaremos o módulo express-session, instalado usando o comando abaixo na raiz do seu projeto:

Para uma abordagem mais escalável e profissional, nosso cookie de sessão não vai armazenar todos os dados da sessão, mas sim apenas seu ID, enquanto que os demais dados estarão em nosso MongoDB que já está prontinho.

O express-session permite armazenar os dados da sua sessão em diferentes mecanismos de persistência, desde que usando os conectores adequados. Como vamos usar Mongo, instale também o módulo de persistência de sessão para MongoDB:

Com isso, agora podemos configurar a nossa aplicação, abra o app.js e adicione as seguintes linhas no topo dele:

Isso apenas declara e inicia nossos módulos recém instalados. Agora vamos adicionar estas linhas para passar a usá-los no app.js, logo no bloco onde você vê vários app.use juntos:

Aqui estou carregando nosso módulo auth.js passando o objeto passport pra ele configurar a estratégia de autenticação. Depois digo para o express-session usar a mesma conexão MongoDB já inicializada com nossa aplicação e que os dados de sessão devem ser apagados automaticamente após 30 minutos, um recurso nativo do MongoDB chamando TTL Index.

Os campos resave e saveUnitialized eu optei por deixar como false para não onerar demais o banco. O primeiro ressalva o cookie de sessão a cada requisição, enquanto que o segundo salva dados de sessões anônimas também. Caso precise de algum deles, apenas mude para true.

E com isso deixamos nossa aplicação ‘pronta’ para receber autenticação, embora que se você rodar esse projeto agora, tirando a tela de login que vai abrir automaticamente, não há segurança alguma ainda.

#6 – Fazendo o login funcionar

Com isso, podemos agora voltar para o arquivo index.js que receberá os posts da tela de login e realizará a verificação de usuário e senha junto ao passport (que vai usar a estratégia que configuramos anteriormente). Vale lembrar que qualquer sistema com o mínimo de segurança deve estar utilizando SSL para que os dados postados pelo formulário não sejam facilmente capturados e lidos na rede (se estiver usando a dica que dei de usar a Umbler, você pode ativar SSL gratuito e de qualidade da Let’s Encrypt ou da CloudFlare).

Primeiro, vamos carregar o passport no index.js, lá no início e vamos adicionar uma function que vai ser muito útil depois para não deixar usuários anônimos acessarem as páginas internas da aplicação:

Chamaremos essa função authenticationMiddleware toda vez que uma requisição solicitar uma página que não seja as públicas (como login). Basicamente ela verifica se a requisição está autenticada (isAuthenticated), caso contrário joga o usuário anônimo para a tela de login com a flag de falha.

Agora vamos criar duas novas rotas nesse arquivo:

A primeira, é um GET /login que devolve uma mensagem apropriada conforme a flag de erro na querystring existir ou não. Você pode aperfeiçoar este comportamento para diferentes mensagens de acordo com diferentes códigos de erro ou coisas do tipo.

Já a segunda function captura requisições POST /login passando ao passport a tarefa de autenticar usando a estratégia local. O objeto de configuração passado como segundo parâmetro da função authenticate define as URLs de sucesso e de falha, conforme o resultado que o passport definir. Note que ali o sucesso redireciona para ‘/chat’, mude para o path adequado da sua aplicação.

Agora, para finalizar essa primeira (e extensa) etapa para criar autenticação profissional com Node.js, precisamos apenas garantir que nenhum usuário anônimo possa acessar a tela de chat do sistema. Para isso, usaremos nossa function authenticationMiddleware como um intermediário entre as requisições GET para as telas privadas da nossa aplicação, que deve ficar como abaixo onde usei como exemplo a rota ‘/chat’ (mude de acordo com sua aplicação):

Também aproveitei e passei o username que acabou de se autenticar como model dessa view para ser usado mais tarde.

Sim, mais tarde. Continuaremos com as demais funcionalidades necessárias para um sistema de login completo como recuperação de senha e cadastro de usuário.

Por ora, você pode cadastrar usuários manualmente (ou usar o adm/123 que pré-cadastramos antes) usando alguma ferramenta bcrypt online para gerar o hash da senha. Depois teste a sua tela de login para ver se ela aceita suas credenciais, se ela redireciona corretamente, tente acessar a tela de chat (ou a tela privada que você definiu) sem fazer login antes ou até mesmo diminua o TTL da sua sessão para ver se ela expira sozinha e te manda de volta para o login.

Erro no Login
Erro no Login

Está funcionando e você confere a segunda parte deste tutorial neste link!

Connection strategy not found

Se você tiver esse erro, essa resposta aqui ajudou alguns alunos: https://stackoverflow.com/questions/34822381/error-connection-strategy-not-found-mongodb.

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js, onde você encontra este mesmo material em formato de videoaula!


Como criar classes em JavaScript (ES6) e Node.js

Existe muita treta no mercado a respeito do JavaScript ser ou não ser orientado à objetos e não vou entrar nesta discussão. A ideia deste artigo é pura e simplesmente apresentar como usar classes e alguns pequenos recursos ligados à orientação à objetos disponibilizados para JS (e consequentemente Node.js) na versão 6 de 2015.

Há quem diga também que JavaScript é melhor aproveitado no paradigma funcional do que no OO, mas como um profissional que trabalhou mais de uma década com Java e C#, confesso que volta e meia me pego usando OO em aplicações Node.js e saber usar dos recursos OO disponibilizados na especificação ES6 é uma mão na roda.

Sem mais delongas, vamos ao código!

Criando classes em JavaScript

Para quem não lembra ou ainda não aprendeu, uma classe é uma especificação, um tipo novo de objeto da sua aplicação. Por exemplo, uma classe Pessoa (inicie classes sempre com letra maiúscula e no singular) irá definir propriedades e funções comuns a pessoas da sua aplicação. Assim, quando for criar pessoas usando esta classe, elas sempre possuirão a mesma estrutura.

A primeira recomendação é que você use uma classe por arquivo JS, tranformando-o em um módulo JS que deverá ser requerido/importado onde se desejar usar essa classe. Esse arquivo deve ter o mesmo nome da classe, como abaixo, onde crio uma classe Cliente.

O próximo passo é criar o construtor dessa classe, uma função especial que inicializa um objeto deste tipo, usando argumentos e processamentos internos para definir as suas propriedades.

Esse construtor acima espera nome, idade e e-mail e os utiliza para definir as propriedades homônimas de todo cliente criado a partir dessa classe. Internamente, o construtor repassa esses valores para as propriedades do objeto, iniciadas com ‘this.’. Variáveis precedidas por ‘this.’ são propriedades do objeto e serão replicadas em todas variáveis que instanciarmos como Cliente mais tarde.

Note também que ele inicializa uma propriedade dataCadastro, de maneira automática e transparente, pegando a data e hora atuais. Esse tipo de processamento pode ser realizado no construtor inclusive para validar e transformar dados passados como argumento (que tal adicionar validações usando Joi?).

O uso da palavra reservada constructor somente pode ser usada nessa função e ela é disparada automaticamente quando criamos um novo objeto Cliente usando a keyword new, como em outras linguagens orientadas a objeto (Java, C#, etc).

Para usar essa classe que criamos, usei o require no módulo Cliente.js em uma constante, e essa constante representa a classe em si. Usando o operador new, instanciei um novo cliente com nome Luiz, idade 31 e e-mail mostrado acima. Se você voltar no trecho de código anterior, onde declarei o construtor, verá que cada um desses argumentos será colocado em uma propriedade interna do cliente e isso se torna evidente quando você imprime o objeto cliente1 no console.

Objeto cliente impresso no console

Cada variável declarada como sendo um cliente tem o seu próprio conjunto de propriedades, mas a mesma estrutura básica, ficou claro?

Assim, se você declarar cliente1, cliente2, etc; cada um terá o seu nome, sua idade, etc. Independente um do outro, mas com o mesmo “esqueleto”.

Mas e os comportamentos?

Funções de classe em Javascript

Toda classe é composta de propriedades e funções. Essas funções, por uma questão de organização, devem ser sempre relativas à responsabilidade da classe em si, e geralmente manipulam ou utilizam as propriedades do objeto em questão. Assim, uma classe cliente terá funções que usam ou manipulam as propriedades do objeto cliente.

Declarar uma função de classe (chamada de método em outras linguagens OO) é feita dentro do escopo da mesma (abre e fecha chaves mais externas). Não há necessidade da palavra function tradicionalmente usada, mas o restante segue a mesma lógica de functions tradicionais.

Como sabemos se estamos manipulando uma propriedade do objeto ou uma variável comum JS? Através do uso da palavra this novamente!

No exemplo acima descrevo que os objetos do tipo/classe Cliente possuem duas funções: isAdult que retorna true/false com base na idade do objeto e outra chamada getFirstName que baseada no nome do objeto/cliente, retorna a primeira parte do mesmo.

Para chamar estas funções você primeiro deve instanciar objetos do tipo Cliente e suas execuções devem produzir retornos conforme propriedades de cada objeto em particular.

No código acima, eu instancio dois clientes com dados diferentes e depois chamo a função isAdult pra ver quais deles são adultos ou não, com base na idade informada na sua criação.

O resultado você pode ver no seu console, mas basicamente o cliente de nome Luiz é adulto, enquanto que o cliente Pedro não é.

Se você reparar bem no meu código anterior, notará que instanciei o segundo objeto sem e-mail (terceiro argumento do construtor). Isso é totalmente permitido no JavaScript, embora possa causar alguma confusão. Por isso, crie alguma lógica interna para obrigar os campos obrigatórios a serem passados, caso eles existam.

Outra sugestão, oriunda de meus tempos como “Javeiro” é que crie funções get e set para cada uma das propriedades internas das suas classes, para evitar que as mesmas sejam acessadas e manipuladas sem qualquer tipo de encapsulamento, uma vez que em JavaScript não temos modificadores de acesso como private, protected, etc. Tecnicamente chamamos isso de métodos acessores.

E por fim, note que você pode ter propriedades internas que, por sua vez, são do tipo de outra classe. Elas podem ser instanciadas com new dentro do próprio construtor, em uma variável antes ou até depois, conforme sua lógica necessitar.

Para este exemplo, considere a classe endereço abaixo:

Note que criei uma função toString() que monta uma String baseada nas propriedades do endereço. Na verdade já existe uma função toString automaticamente em todas classes JavaScript, mas estamos sobrescrevendo seu comportamento padrão através de declaração de outra função de mesmo nome. Isso é chamado de sobrescrita na orientação à objetos, uma das formas conhecidas de sobrecarga de método/função.

Agora altere a nossa classe Cliente para que eles possuam uma propriedade endereço também:

Para testar o uso de objetos Endereco como propriedade do Cliente, meu index.js vai ficar assim:

O resultado é uma frase como “Luiz mora em Tupis 125…”. Isso porque a função toString de cada objeto endereço é automaticamente chamada quando usamos eles na concatenação de Strings. Acaba acontecendo a mesma coisa que se eu chamasse dessa forma:

O uso de objetos dentro de outros objetos é o que chamamos de associação, sendo que podemos ter associações de agregação ou de composição. Bacana, não?

Propriedades e funções estáticas em JavaScript

E para encerrar este artigo, vamos falar de mais uma característica de Orientação à Objetos implementada no JavaScript: as propriedades e funções estáticas.

Um componente estático (seja ele uma função ou propriedade) é compartilhado entre todos objetos da mesma classe e não necessita que a mesma seja instanciada para que o mesmo exista e possa ser usado/manipulado. Não confundir com as constantes, pois você pode mudar uma característica estática, mas se fizer isso, vai mudar para TODOS objetos que a possuem.

Para declarar uma propriedade estática, basta declará-la com a palavra-reservada static (ao invés de var, let ou const) dentro do escopo da classe. Abaixo, dei apenas um exemplo (uso questionável nesse caso, apenas me faltou criatividade para outro exemplo, hehe).

O que muda neste caso ao invés de usar um const? O ponto principal é que essa variável é da CLASSE e não do OBJETO, para usá-la você deve chamar “classe.propriedade”. Outro ponto é que você pode mudar o valor desta propriedade em tempo de execução ao contrário de const e o último ponto é que se mudá-la, muda para a CLASSE, independente dos OBJETOS, como no exemplo abaixo.

Note que independe de existir ou não algum objeto, a propriedade idadeAdulto existe para a CLASSE e, diferente de const, PODE ser modificada. Ao usar static, você torna esta propriedade global para a classe.

E o mesmo pode ser feito com funções, como mostra a função static abaixo da classe Endereco que retorna um array de UFs existentes no Brasil.

Para usar, você também não instancia um objeto endereço e apenas chama direto classe.funcao.

Eu poderia ter usado o array resultante da função static, mas resolvi apenas imprimi-lo, acredito que você tenha entendido a ideia.

Existem muitos outros conceitos relacionados à orientação à objetos presentes na linguagem JavaScript moderna e consequentemente no Node.js, mas por ora, os conceitos mais importantes ligados a classes em JS eu apresentei neste artigo.

Espero que tenham gostado!

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