Recentemente escrevi um tutorial ensinando como utilizar o Passport, uma popular lib JavaScript para construção de middlewares de autenticação. Neste tutorial, fiz toda a gestão de sessão e de autenticação em memória, usando um array simples, para fins didáticos.
A gestão de sessão não é incomum ser feita em memória, mas possivelmente você irá querer a gestão dos seus usuários persistida em um banco de dados, certo?
No tutorial de hoje, vamos adaptar o projeto anterior (então baixe ele na sua máquina) para uso com MongoDB, um popular banco de dados NoSQL.
Atenção: este é um tutorial intermediário. Parto do pressuposto aqui que você já conhece Node.js e MongoDB em níveis básicos e também que já realizou o tutorial anterior, onde criamos este projeto com persistência em array.
Para ver a construção de uma aplicação web completa usando Node.js e MongoDB, conheça meu curso online e meu livro.
#1 – Configurando o banco de dados e variáveis de ambiente
Os dados de sessão dos usuários e os próprios usuários vão ficar armazenados em um banco MongoDB, substituindo o array do tutorial anterior.
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. Outra opção é o MongoDB Atlas, que tem um plano gratuito.
Voltando ao nosso projeto Node.js, instale o driver do MongoDB para Node:
1 |
npm i mongodb dotenv-safe |
Note que instalei também o pacote 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.
Para que o dotenv-safe funcione (ensino em videoaulas do meu curso de Node.js e MongoDB), você precisa criar dois arquivos novos: “.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 três:
1 2 3 4 |
#.env.example commit to repo MONGO_CONNECTION= MONGO_DB= SESSION_SECRET= |
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):
1 2 3 4 |
#.env DON'T commit to repo MONGO_CONNECTION=mongodb://localhost:27017/passport MONGO_DB=passport SESSION_SECRET=123 |
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, onde uso a variável de ambiente como connection string:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(async () => { require('dotenv-safe').config(); const app = require('../app'); const debug = require('debug')('nodejs-passport-mongodb:server'); const http = require('http'); const mongoClient = require("mongodb").MongoClient; try { const conn = await mongoClient.connect(process.env.MONGO_CONNECTION); console.log("conectou no banco de dados!"); global.db = conn.db(process.env.MONGO_DB); } catch (err) { return console.log(err); } //daqui pra baixo não mexi em nada ... })(); |
Repare que eu encapsulei todo o conteúdo do bin/www em um IIFE. Isso foi necessário para conseguir usar async/await na conexão com o banco.
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 no banco passport, na coleção users, um usuário com os seguintes dados:
1 |
db.users.insert({username: "adm", password: "$2a$06$HT.EmXYUUhNo3UQMl9APmeC0SwoGsx7FtMoAWdzGicZJ4wR1J8alW", email: "[email protected]"}); |
Coloque no campo email, um email seu de verdade, pois precisaremos dele funcionando mais tarde. Note que o password do usuário está criptografado em formato de hash Bcrypt. Esta senha na verdade é o hash da palavra “123”. 🙂
#2 – Refatorando o app.js
Antes de mais nada, no nosso app.js nós definimos um secret que é usado pelo Passport para criptografar algumas coisas dele. Hoje este secret está chumbado, mas vamos mudar isso para usar uma variável de ambiente, como abaixo.
1 2 3 4 5 6 7 8 9 |
require('./auth')(passport); app.use(session({ secret: process.env.SESSION_SECRET,//configure um segredo seu aqui, resave: false, saveUninitialized: false, cookie: { maxAge: 30 * 60 * 1000 }//30min })) app.use(passport.initialize()); app.use(passport.session()); |
Além disso, vamos instalar uma nova dependência no projeto para que o express-session use o MongoDB ao invés da memória para gerenciar as sessões. O express-session é muito extensível e basta instalarmos o conector para o banco de dados apropriado e fazer umas poucas configurações.
1 |
npm i connect-mongo |
No app.js, no mesmo bloco de configuração do Passport, carregue o middleware de sessão do connect-mongo e adicione-o na chamada do session, para que ele passe a apontar para o MongoDB corretamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const MongoStore = require('connect-mongo'); require('./auth')(passport); app.use(session({ store: MongoStore.create({ autoRemove: 'native', mongoUrl: process.env.MONGO_CONNECTION, ttl: 30 * 60 // = 30 minutos de sessão }), secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { maxAge: 30 * 60 * 1000 }//30min })) app.use(passport.initialize()); app.use(passport.session()); |
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 string de conexão com o MongoDB e que os dados de sessão devem ser apagados automaticamente após 30 minutos, um recurso nativo do MongoDB chamando TTL Index.
#3 – Refatorando o auth.js
Agora que temos nosso banco rodando, vamos refatorar as partes do projeto que usavam o array em memória para fazer a gestão dos usuários, abrindo nosso auth.js e antes de tudo, removendo o array de users e depois refatorando as duas funções de findUser como abaixo.
1 2 3 4 5 6 7 8 |
function findUser(username) { return global.db.collection("users").findOne({ "username": username }); } function findUserById(id) { const ObjectId = require("mongodb").ObjectId; return global.db.collection("users").findOne({ _id: ObjectId(id) }); } |
Assim, ao invés de estarmos procurando em um array, estaremos procurando diretamente no banco de dados.
Como as funções do MongoDB são assíncronas, ou usamos callbacks, promises ou ainda async/await, que foi a opção que escolhi aqui. Devido a isso, temos de alterar as funções que chamam as findUser para que usem async/await:
1 2 3 4 5 6 7 8 |
passport.deserializeUser(async (id, done) => { try { const user = await findUserById(id); done(null, user); } catch (err) { done(err, null); } }); |
e essa aqui também:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, async (username, password, done) => { try { const user = await findUser(username); // usuário inexistente if (!user) { return done(null, false) } // comparando as senhas const isValid = bcrypt.compareSync(password, user.password); if (!isValid) return done(null, false); return done(null, user); } catch (err) { done(err, false); } } )); |
E com isso você tem novamente a sua aplicação web em Node.js funcionando e exigindo autenticação, mas agora com o MongoDB como persistência de dados!
Confira a parte 2 deste tutorial neste link.
Quer aprender a construir aplicações web usando Node.js e MongoDB com a mesma didática deste tutorial? Conheça o meu curso online ou meu livro.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.