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 MySQL, um popular banco de dados SQL.
Atenção: este é um tutorial intermediário. Parto do pressuposto aqui que você já conhece Node.js e MySQL 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 MySQL, conheça meu curso online (banner abaixo) 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 MySQL, substituindo o array do tutorial anterior.
Eu já expliquei tudo que você precisa saber sobre como configurar o ambiente MySQL 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 MySQL deles usando os créditos que você ganha quando se cadastra.
Voltando ao nosso projeto Node.js, instale o driver do MySQL para Node:
1 2 3 |
npm i mysql2 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 fullstack JS), 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, as abaixo:
1 2 3 4 5 6 7 8 9 |
#.env.example commit to repo MYSQL_HOST= MYSQL_PORT= MYSQL_USER= MYQL_PASSWORD= MYSQL_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 5 6 7 8 9 10 |
#.env commit to repo SESSION_SECRET=123 MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USER=root MYQL_PASSWORD=luiztools MYSQL_DB=passport SESSION_SECRET= |
Agora vamos criar um módulo db.js, que vai fazer a gestão da conexão com o MYSQL. Crie-o na raiz do projeto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async function connect() { if (global.connection && global.connection.state !== 'disconnected') return global.connection; const mysql = require("mysql2/promise"); const connection = await mysql.createConnection(`mysql://${process.env.MYSQL_USER}:${process.env.MYSQL_PASSWORD}@${process.env.MYSQL_HOST}:${process.env.MYSQL_PORT}/${process.env.MYSQL_DB}`); console.log("Conectou no MySQL!"); global.connection = connection; return connection; } module.exports = { connect } |
Esta função apenas garante que teremos uma única conexão com o banco de dados sendo reaproveitada em toda aplicação. Existem técnicas bem mais avançadas que isso, como usar um ORM por exemplo. Note que exportei a função ao final, para que quando este módulo for carregado a primeira vez, possamos criar a conexão.
Agora modifique o seu bin/www (geramos este projeto com o express-generator, lembra?) para que importe o nosso db.js e chame o connect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/bin/env node (async () => { /** * Module dependencies. */ require('dotenv-safe').config(); const app = require('../app'); const debug = require('debug')('nodejs-passport-mysql:server'); const http = require('http'); try { await require('../db').connect(); } catch (err) { return console.log(err); } //daqui pra baixo eu não mexi, apenas feche o iife ao final })() |
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 possuirá as funções de manipulação com o banco e mesmo que a gente carregue ele em outros pontos da aplicação, ele usará o que já está em cache. Também coloquei como primeira linha o carregamento das variáveis de ambiente. Caso o .env não exista, teremos um erro. Caso não consiga se conectar ao banco, teremos um erro (certifique-se criar um banco com o nome ‘passport’ no seu mysql, ou mude a connection string de acordo). 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 MySQL local ou remoto e crie no banco ‘passport’, a tabela ‘users’, conforme SQL abaixo:
1 2 3 4 5 6 7 8 9 10 |
CREATE TABLE `passport`.`users` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(150) NOT NULL, `password` VARCHAR(150) NULL, `email` VARCHAR(150) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `username_UNIQUE` (`username` ASC) VISIBLE, UNIQUE INDEX `email_UNIQUE` (`email` ASC) VISIBLE); |
Depois, adicione nela um usuário com os seguintes dados:
1 2 3 |
INSERT INTO `passport`.`users` (`username`, `password`, `email`) VALUES ('adm', '$2a$06$HT.EmXYUUhNo3UQMl9APmeC0SwoGsx7FtMoAWdzGicZJ4wR1J8alW', '[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 10 11 |
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 MySQL 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 2 3 |
npm i express-mysql-session |
No app.js, no mesmo bloco de configuração do Passport, carregue o middleware de sessão do express-mysql-session e adicione-o na chamada do session, para que ele passe a apontar para o MySQL corretamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const MySQLStore = require('express-mysql-session')(session); require('./auth')(passport); app.use(session({ store: new MySQLStore({ host: process.env.MYSQL_HOST, port: process.env.MYSQL_PORT, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DB }), 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 MySQL e que os dados de sessão devem ser apagados automaticamente após 30 minutos.
#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 para que usem nosso banco de dados. Vamos começar voltando ao nosso db.js e criando duas novas funções lá.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
async function findUser(username) { const conn = await connect(); const [rows] = await conn.query(`SELECT * FROM users WHERE username=? LIMIT 1`, [username]); if (rows.length > 0) return rows[0]; else return null; } async function findUserById(id) { const conn = await connect(); const [rows] = await conn.query(`SELECT * FROM users WHERE id=? LIMIT 1`, [id]); if (rows.length > 0) return rows[0]; else return null; } module.exports = { connect, findUser, findUserById } |
Agora, vamos abrir nosso auth.js, removendo o array de users, removendo as funções de findUser e findUserById originais e depois refatorando as duas chamadas das funções antigas para que usem as novas do objeto db, como abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
const bcrypt = require('bcryptjs'); const LocalStrategy = require('passport-local').Strategy; module.exports = function (passport) { passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser(async (id, done) => { try { const db = require('./db'); const user = await db.findUserById(id); done(null, user); } catch (err) { done(err, null); } }); passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, async (username, password, done) => { try { const db = require('./db'); const user = await db.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); } } )); } |
Assim, ao invés de estarmos procurando em um array, estaremos procurando diretamente no banco de dados.
Como as funções do MySQL são assíncronas, ou usamos callbacks, promises ou ainda async/await, que foi a opção que escolhi aqui. Devido a isso, tive de alterar as funções que chamam as findUser para que usem async/await.
E com isso você tem novamente a sua aplicação web em Node.js funcionando e exigindo autenticação, mas agora com o MySQL como persistência de dados!
Quer aprender a publicar esta aplicação na Amazon? Dê uma olhada neste tutorial!
Ou então publique na Umbler, que nem precisa de tutorial. 🙂
Quer aprender a construir aplicações web usando Node.js e MySQL 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.