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!


Administrando MongoDB: Replicação/Espelhamento

Este artigo possui versão em videoaula, disponível para os alunos do meu curso de Node.js e MongoDB!

Dando continuidade à série de artigos sobre administração de instâncias MongoDB, quero falar no artigo de hoje sobre replicação ou espelhamento de instâncias, uma forma muito comum de garantir alta disponibilidade de aplicações através da redundância de databases.

Caso não tenha lido o artigo anterior, nele eu falei sobre criação de usuário e senha com diferentes perfis de acesso em instâncias de Mongo.

Claro que se você usa serviços profissionais em nuvem, geralmente bem caros, você geralmente ou não vai se preocupar com replicação (o serviço já faz automaticamente) ou vai configurar isso facilmente através de algum painel de controle.

No entanto, caso você tenha uma pequena aplicação ou mesmo não tenha grana para serviços como Atlas e Mlab, a dica de hoje pode te ajudar bastante.

Se você não sabe nada ou muito pouco de MongoDB, sugiro não ler este artigo e procurar a minha série de MongoDB para Iniciantes em NoSQL. Ou então meu livro MongoDB para Iniciantes, já que o conteúdo deste artigo é um pouco mais avançado que o normal.

O que é uma Replica Set?

No MongoDB chamamos o popular espelhamento de replica set. Um replica set é um conjunto de processos que mantém o mesmo conjunto de dados (dataset). No MongoDB, assim como em outros bancos modernos, permite fazer este espelhamento de maneira bem simples e é um dos pilares de segurança, uma vez que derrubar um servidor de banco de dados é uma das formas de tirar uma aplicação do ar, sendo que replica sets dificultam isso.

Não obstante, replica sets muitas vezes auxiliam na velocidade de leitura (pois diferentes usuários podem estar lendo de diferentes réplicas ao mesmo tempo) e podem auxiliar na velocidade de acesso, caso você possua réplicas em diferentes áreas geográficas. Outros usos para replica sets incluem backup (você mantém uma réplica que ninguém acessa, apenas para backup near-realtime), reporting (você mantém uma réplica apenas para leitura e extração de relatórios) e disaster recovery (você chaveia para ela, em outro continente, em caso de perder o datacenter principal).

Basicamente, a arquitetura de uma replica set é constituída de um primário e os secundários. Como mostra a imagem abaixo, exemplificando o recomendado que é 3 instâncias (quem tem 3 sempre vai ter 2, quem tem 2 tem 1 e quem tem só 1, não tem nenhum).

Arquitetura MongoDB Replica Set
Arquitetura MongoDB Replica Set

Basicamente o funcionamento é assim:

  • somente o primário recebe escritas;
  • todos recebem leituras;
  • quando escrevem no primário, ele replica para todos os secundários;
  • todos monitoram todos (heartbeat);
  • se o primário cair, um secundário assume como primário e passa a receber as escritas;

Preferencialmente, todas instâncias devem possuir a mesma versão do MongoDB, para evitar problemas. E com isso finalizamos o básico que você deve saber sobre replica sets antes de usá-las.

Criando um Replica Set

Replicar instâncias de MongoDB é muito simples, ao menos em um nível básico. Primeiro, suba com mongod uma instância de Mongo apontando os dados para uma pasta qualquer e passando o argumento replSet, como abaixo, que indica que esta instância faz parte do Replica Set “rs0”.

Note que mudei a porta default, pois como vou subir mais de uma instância na mesma máquina (apenas para fins de estudo) precisarei usar portas diferentes. Agora suba outra instância de mongod apontando os dados para outra pasta, com outra porta mas mantendo o argumento replSet para a mesma Replica Set.

O próximo passo é inicializar o Replica Set com estas duas instâncias. Para fazer isso, abra outra janela de terminal e se conecte via mongo em apenas uma das instâncias, por exemplo, a primeira:

Uma vez conectado nesta instância, rode o comando abaixo que inicializa a Replica Set com todas as réplicas que você possui. Aqui o recomendado é que se use os DNS públicos das instâncias e não os IPs, para maior flexibilidade.

Com isso, o Replica Set começou a funcionar. O primary deve ser definido por eleição entre as réplicas, automaticamente. Se você quiser definir isso manualmente, adicione uma propriedade priority ao objeto member com um valor de 0 a 1000 (maior é melhor).

Por padrão, os secundários servem apenas como backup, ou seja, não podem ser acessados para leitura. Se quiser liberar a leitura nos secundários (lembrando que pode haver diferença mínima nos dados pois a replicação não é instantânea), use o comando abaixo nesta mesma sessão:

Já escrita é só no primário mesmo, não tem o que fazer. Agora se você se conectar a qualquer uma das instâncias da replica set notará que o console informa se você está no primário ou em um dos secundários, como mostra na imagem abaixo.

Duas réplicas e um cliente
Duas réplicas e um cliente

Uma coisa bacana é que, se você já tiver dados em uma das instâncias quando criar a Replica Set, eles automaticamente serão replicados assim que o Replica Set for inicializado, ou seja, pode ser uma estratégia de backup bem interessante subir de vez em quando um Replica Set para espelhar seu banco.

No mais, todo dado que você adicionar no primário, a partir do momento que criou a Replica Set, serão replicados para TODOS secundários assim que possível (geralmente em poucos segundos, dependendo do volume e distância geográfica).

Caso o primário caia, uma nova eleição será feita entre os secundários e um deles vai assumir. Por isso a importância de fazer Replica Sets com no mínimo 3 membros, embora funcione com 2, como fiz no exemplo.

Usando um Replica Set

E ao usar via aplicação, o que muda? Se você se conectar diretamente ao primário (para leitura e escrita) ou a um secundário (para leitura somente), nada vai mudar e os dados apenas estarão sendo replicados em background. Claro, se cair a instância que você está conectado, não vai adiantar estar replicado pois sua aplicação não conhece o Replica Set, mas apenas uma instância específica.

Agora, se você quer realmente aproveitar todos benefícios desta abordagem, o recomendado é se conectar informando o Replica Set, mudando sua connection string para algo similar ao abaixo:

Caso tenha usuário e senha, adicione-os à frente da primeira instância como faria normalmente e adicione mais um parâmetro no final da URL para informar o banco de autenticação, como abaixo:

Não é necessário listar todos os servidores da Replica Set na connection string, pois ao se conectar a um deles (o mais próximo e disponível) e ele informar que está na replicaSet informada na connection string, o client vai receber a informação de TODOS os demais membros da Replica Set, mesmo que alguns não estejam listados na connection string.

Isso porque a adição e remoção de membros do Replica Set acontece de maneira independente à aplicações que a usam, certo?

Ainda assim é recomendado informar mais de um membro na connection string pois pode ser que alguns membros estejam down no momento da conexão, aí o client vai tentar conectar no próximo.

Segurança em Replica Sets

Caso você esteja usando autenticação nos membros da sua Replica Set, e eu sugiro que o faça, é sempre mais fácil rodar as instâncias sem autenticação como eu fiz para fazer as configurações, no entanto, quando for colocar em produção vai precisar dela novamente. No entanto, depois de tudo configurado, se você rodar as instâncias com –auth, vai receber o erro de “unauthorized replSetHeartbeat …”.

O processo de heartbeat entre os membros do Replica Set exige que eles confiem uns nos outros. Uma forma bem comum de fazer isso é através de keyfiles. Para criar um keyfile, use os comandos abaixo no terminal:

Troque caminho-pasta para um caminho na pasta da sua instância de Mongo, deixe o key como está e copie o mesmo arquivo para as demais instâncias da Replica Set.

Agora quando for executar as suas instâncias, inclua o argumento –keyFile, como no exemplo abaixo, além do argumento –auth, é claro.

Com isso, as instâncias confiarão umas nas outras para replicação, uma vez que possuem a mesma chave e você terá um Replica Set seguro.

Com isso eu encerro mais este artigo sobre administração de MongoDB. Deixe nos comentários o que achou deste artigo!

* Espero que este artigo tenha sido útil para você que está aprendendo Node.js e MongoDB. Para conteúdo mais aprofundado, recomendo meus livros. Para videoaulas, recomendo o meu curso online.

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

Administrando MongoDB: User & Password

Este artigo possui versão em videoaula, exclusiva para os alunos do meu curso de Node.js e MongoDB.

Nesta altura do campeonato é bem possível que você saiba que adoro Node.js e MongoDB. Mas caso esteja chegando agora, trabalho com estas tecnologias desde 2015, tenho alguns livros publicados sobre estes assuntos e até um curso online em videoaulas.

No entanto, eu estou mais para o lado “programador” destas duas tecnologias do que para o lado “administrador”, nunca fui um “cara de infra”. Ainda assim, eu sei me virar e sei os fundamentos básicos a ponto de poder lhe ajudar nos primeiros passos de administração de um servidor MongoDB, por exemplo.

Embora a opção mais segura e confiável seja utilizar serviços em nuvem como Atlas e Mlab, eles são muito caros, o que inviabiliza pequenos projetos. Pensando nesse segundo público, no artigo de hoje, o primeiro de uma série sobre administração de MongoDB, vou ensinar como você pode adicionar usuário e senha no seu servidor MongoDB, uma vez que por padrão ele vem “aberto”.

Atenção: existe estudos que mostram que existem mais de 30.000 instâncias de MongoDB expostas sem usuário e senha na Internet. Não faça parte dessa lista de tolos, implemente autenticação no seu banco de dados.

Subindo uma instância nova de MongoDB

Não vou repetir aqui todo o passo a passo de MongoDB para iniciantes em NoSQL. Essa é uma ótima série aqui do blog e recomendo que dê uma lida nela caso esteja começando agora com Mongo. Baixe a versão community (gratuita) do MongoDB no site oficial e coloque uma instância para rodar usando o comando abaixo (dentro do diretório bin da sua instalação de Mongo).

A variável dbpath eu apontei para uma pasta mongoauth/data por uma questão de organização (crie essa pasta se quiser seguir a mesma linha de raciocínio que a minha).

Já a variável port eu coloquei para definir outra porta para o meu MongoDB por uma questão de segurança. Atacantes de bancos de dados sempre usam as portas padrões dos bancos para tentar invadi-los e é um chamariz para bandido deixar o MongoDB na porta 27017, troque para outro à sua escolha (mais dicas como essa, aqui).

Para testar se sua instância está funcionando, em outra janela do terminal rode o comando abaixo para se conectar, sem qualquer credencial ao banco local.

Lembre-se de usar a mesma porta da instância que você subiu anteriormente.

Terminais mongod e mongo
Terminais mongod e mongo

Agora, para testarmos via software, instale o Node.js na sua máquina a partir do site oficial (tanto faz a versão) e rode o comando abaixo para inicializar um projeto na sua pasta mongoauth que foi criada anteriormente.

Apenas siga respondendo às perguntas que ele fizer e depois rode o comando abaixo para instalar a dependência do MongoDB Node.js Driver que vamos usar.

Depois crie um arquivo index.js na raiz dessa pasta para colar o código Javascript abaixo, que apenas testa a conexão com o Mongo e printa no console o resultado.

Esse código não é o foco aqui do artigo, mas ele se conecta na instância local do Mongo, porta 27018 e, se funcionar, imprime as informações da conexão. Se não funcionar, imprime o erro.

Para testar, apenas rode o comando abaixo no terminal, dentro da pasta do projeto (para funcionar, certifique-se de que a janela de terminal do mongod esteja rodando ou derrube-a para simular erro).

Exemplo de sucesso:

Conexão sem usuário OK
Conexão sem usuário OK

Exemplo de erro:

Erro de conexão no Mongo
Erro de conexão no Mongo

Note que o erro é bem claro em informar que foi uma falha de rede, que ele não encontrou um banco Mongo no endereço informado. Mais pra frente vamos simular erros de autenticação. Agora que temos esta estrutura mínima pronta, vamos adicionar usuário e senha neste banco, para não deixá-lo mais aberto.

Adicionando usuário e senha admin

O primeiro passo de autenticação é criarmos um usuário e senha de administrador, certo? Mais tarde podemos criar usuários com menos privilégios para bancos específicos, mas por ora, temos de ter o master.

Certifique-se de que sua instância do mongod continua rodando. Conecte-se à ela usando outra janela de terminal com o utilitário mongo (fizemos isso antes, lembra?). Agora execute os seguintes comandos no terminal mongo:

O primeiro comando aponta para o banco admin do MongoDB, o equivalente à database master de outros SGBDs. Já o segundo, cria um novo usuário com o nome, senha e perfil de acesso especificado. “userAdminAnyDatabase” vai conferir poderes totais à esse usuário, então crie ele com uma senha bem forte, por favor.

Agora, derrube a sua instância de mongodb e suba ela de novo, mas com uma variação no comando:

O –auth fará com que o seu banco esteja “protegido” com usuário e senha a partir de agora. Se você tentar se conectar com o utilitário mongo, sem passar usuário e senha, até vai rolar localmente, mas não vai conseguir fazer nada pois nenhum banco de dados será visível para você (experimente um ‘show databases’ e verá que não retorna nada) e mesmo que tente criar um, consultar, etc não vai rolar.

Agora, tente se conectar via terminal usando o comando abaixo, em que passo usuário e senha:

E depois, com um ‘show databases’ você vai ver que possui privilégios totais nesta instância de Mongo.

Agora, se tentar passar uma senha errada, terá como retorno um “authentication failed”. E o nosso código Node.js? O mesmo vale para as conexões anônimas locais: não vai dar erro, mas também não vai fazer nada nessa instância de Mongo.

Para poder fazer suas consultas, inserções, etc será necessário se autenticar. Mas não vamos nos autenticar como admin em uma aplicação, certo? Vamos criar um user específico para ela.

Adicionando usuário e senha de aplicação

Usuário admin é para o administrador da instância de Mongo, onde podem existir diversos bancos de dados. Considerando que cada aplicação tenha o seu banco, crie um usuário isolado para cada uma. O processo é idêntico ao realizado para criar o user admin, mas agora muda a role e o nome do usuário, como abaixo:

Aqui foi criado um usuário luiz, dentro da database teste com permissão de “dono” nesta database, ou seja, ele pode fazer leituras e escritas livremente em todas coleções da database teste.

Reforço que criei o user na própria database teste, deixando a database admin apenas para administradores. Agora, para se conectar ao banco teste localmente será necessário o comando abaixo:

Note que no último parâmetro coloquei a base de dados onde existe o usuário e senha informados. Já no Node.js, passamos essas informações na própria connection string, respeitando URI encoding (caracteres especiais tem de ser codificados para URI):

A sintaxe é “usuário:senha@host:porta/?authSource=banco-com-user”. Experimente colocar uma senha errada e você deve receber um erro de authentication failed no seu console do Node.js. E com isso encerramos este artigo de administração de MongoDB. Espero ter ajudado!

Quer aprender tópicos mais avançados em administração de MongoDB? Que tal espelhamento de instâncias?

Espero que este artigo tenha sido útil para você que está aprendendo Node.js e MongoDB. Para conteúdo mais aprofundado, recomendo meus livros. Para videoaulas, recomendo o meu curso online.

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