HTTP e HTTPS são protocolos da Internet que permitem que os dados sejam enviados pela Internet enviando uma solicitação por meio de um navegador da Web. Por serem sem estado, cada solicitação enviada ao navegador é tratada de forma independente. Isso significa que o navegador não consegue lembrar a origem de uma solicitação, mesmo que o mesmo usuário a faça. As sessões HTTP resolvem esse problema.
Este artigo analisará o gerenciamento de sessões e como ferramentas como Passport, Redis e MySQL podem nos ajudar a gerenciar sessões do Node.js. Vamos mergulhar.
Como funcionam as sessões HTTP?
As sessões HTTP permitem que os servidores da Web mantenham a identidade do usuário e armazenem dados específicos do usuário em várias interações de solicitação/resposta entre um aplicativo cliente e um aplicativo Web. Quando um cliente faz login no aplicativo, o servidor gera um SessionID. A sessão é salva na memória usando um mecanismo de armazenamento persistente não replicado de servidor único. Exemplos de tais mecanismos incluem persistência JDBC, persistência do sistema de arquivos, persistência de sessão baseada em cookie e replicação na memória. Quando o usuário envia uma solicitação subsequente, o ID da sessão é passado no cabeçalho da solicitação e o navegador verifica se o ID corresponde a algum no armazenamento de memória e concede acesso ao usuário até que a sessão expire.
As sessões HTTP armazenam os seguintes dados na memória:
- Especificações sobre a sessão (identificador de sessão, hora de criação, hora do último acesso etc.)
- Informações contextuais sobre o usuário (por exemplo, status de login do cliente)
O que é Redis?
O Redis (Remote Dictionary Server) é um armazenamento de dados de valor-chave na memória rápido, de código aberto, usado como banco de dados, cache, agente de mensagens e fila.
O Redis tem tempos de resposta abaixo de milissegundos, permitindo milhões de solicitações por segundo para aplicativos em tempo real em setores como jogos, tecnologia de anúncios, finanças, saúde e IoT. Como resultado, o Redis é agora um dos mecanismos de código aberto mais populares, tendo sido nomeado o banco de dados "Most Loved" pelo Stack Overflow cinco anos consecutivos. Devido ao seu rápido desempenho, o Redis é uma escolha popular para armazenamento em cache, gerenciamento de sessões, jogos, tabelas de classificação, análises em tempo real, geoespacial, carona, bate-papo/mensagens, streaming de mídia e pub/sub-apps.
O que estamos construindo?
Para demonstrar o gerenciamento de sessão no Node.js, criaremos um aplicativo simples de inscrição e login. Os usuários se inscreverão e entrarão neste aplicativo fornecendo seu endereço de e-mail e senha. Uma sessão é criada e salva no armazenamento do Redis para solicitações futuras quando um usuário faz login. Quando um usuário faz logout, excluiremos sua sessão. Chega de falar; vamos começar!
Pré-requisitos
Este tutorial é uma demonstração prática. Certifique-se de ter o seguinte instalado antes de começar:
- Node.js
- Redis CLI
- Banco de dados MySQL
- Artipo
O código para este tutorial está disponível no meu repositório Github. Sinta-se clonar e acompanhar.
Configuração do projeto
Vamos começar criando uma pasta de projeto para a aplicação com o comando abaixo:
mkdir Session_management && cd Session_management
Em seguida, inicialize um aplicativo Node.js para criar um arquivo package.json com o comando abaixo:
npm init -y
O
-y
flag no comando acima diz ao npm para usar a configuração padrão. Agora crie a seguinte estrutura de pastas no diretório raiz do seu projeto. Com nosso package.json criado, vamos instalar o pacote necessário para este projeto na próxima seção.
Instalando dependências
Vamos instalar as seguintes dependências para nosso aplicativo:
- Bcryptjs - Este módulo será usado para fazer o hash da senha do usuário.
- Conectar-redis - Este módulo fornecerá armazenamento de sessão Redis para Express.
- Sessão expressa - Este módulo será usado para criar sessões.
- Ejs - Este módulo é nosso mecanismo de modelo
- Passaporte - Este módulo será usado para autenticação do usuário
- Passaporte-local - Este módulo será usado para autenticação local de nome de usuário e senha
- Sequelizar - Este módulo é o nosso MySQL ORM para conectar nosso aplicativo ao banco de dados MySQL.
- Dotenv - Este módulo será usado para carregar nossas variáveis de ambiente.
Use o comando abaixo para instalar todas as dependências necessárias.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Aguarde a conclusão da instalação. Quando a instalação estiver concluída, prossiga com a configuração do banco de dados MySQL na próxima seção.
Configurando o banco de dados MySQL
Vamos criar um banco de dados MySQL para nosso aplicativo. Mas antes disso, execute o comando abaixo para criar uma conta de usuário MySQL.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Agora crie um banco de dados session_db, e conceda ao novo usuário acesso ao banco de dados com o comando abaixo:
#Create database
CREATE DATABASE session_db;
#grant access
GRANT ALL PRIVILEGES ON session_db TO 'newuser'@'localhost';
ALTER USER 'newuser'@'localhost' IDENTIFIED WITH mysql_native_password BY '1234';
Agora recarregue todos os privilégios com o comando abaixo:
FLUSH PRIVILEGES;
Com nossa configuração de banco de dados MySQL, vamos criar nossos
users
modelo de banco de dados na próxima seção. Criar servidor expresso
Com nossa configuração de banco de dados MySQL, vamos criar um servidor expresso para nossa aplicação. Abra o arquivo src/server.js e adicione o snippet de código abaixo:
const express = require("express");
const app = express();
const PORT = 4300;
//app middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//Redis configurations
//Configure session middleware
//Router middleware
app.listen(PORT, () => {
console.log(`Server started at port ${PORT}`);
});
No trecho de código acima, criamos um servidor expresso, que escutará as solicitações na porta 4300. Em seguida, analisamos as solicitações recebidas com cargas JSON usando o
express.json()
middleware e analise as solicitações recebidas com urlencoded
usando Express.urlencoded()
middleware. Crie o modelo de banco de dados
Neste ponto, nosso servidor Express está definido. Agora vamos criar um
Users
model para representar os dados do usuário, veremos o banco de dados usando Sequelize
. Abra o src/models/index.js
arquivo e adicione o trecho de código abaixo. const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize({
host: "localhost",
database: "session_db",
username: "newuser",
password: "1234",
dialect: "mysql",
});
exports.User = sequelize.define("users", {
// Model attributes are defined here
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
});
No trecho de código acima, importamos
Sequelize
e DateTypes
de sequelize
para se conectar ao nosso banco de dados MySQL e atribuir um tipo de dados às propriedades do nosso modelo. Em seguida, nos conectamos ao MySQL criando um sequelize
instância do Sequelize
class e passando nossas credenciais de banco de dados. Por exemplo, com o sequelize
instância, definimos nosso modelo e suas propriedades. Queremos apenas os campos id, email e senha deste tutorial. Mas sequelize cria dois campos adicionais, o createdAt
e updatedAt
Campos. Configurar Passaporte e Redis
Para manipular e armazenar as credenciais do nosso usuário, usaremos e configuraremos o
Redis
. Para fazer isso, abra o src/index.js
arquivo e importe as seguintes dependências abaixo:const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
Em seguida, localize a área comentada
//Redis configurations
e adicione o trecho de código abaixo:const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
No trecho de código acima, estabelecemos uma conexão com nosso banco de dados, que gerenciará os dados de nome de usuário de nosso usuário.
Em seguida, localize a área comentada
//Commented session middleware
e adicione o trecho de código abaixo://Configure session middleware
const SESSION_SECRET = process.env.SESSION_SECRET;
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // if true only transmit cookie over https
httpOnly: false, // if true prevent client side JS from reading the cookie
maxAge: 1000 * 60 * 10, // session max age in milliseconds
},
})
);
app.use(passport.initialize());
app.use(passport.session());
No snippet de código acima, criamos um
SESSION_SECRET
variável em um .env
para manter nosso segredo de sessão, depois criei um middleware de sessão e usei o Redis como nossa loja. Para que a sessão funcione adicionamos mais dois middlewares o passport.initialize()
e passport.session()
. Criar controladores de aplicativos
Com nosso Redis e configuração de sessão expressa, criaremos uma rota para lidar com as informações dos usuários. Para fazer isso, abra o
src/controllers/index.js
arquivo e adicione o trecho de código abaixo:const { User } = require("../models");
const bcrypt = require("bcrypt");
exports.Signup = async (req, res) => {
try {
const { email, password } = req.body;
//generate hash salt for password
const salt = await bcrypt.genSalt(12);
//generate the hashed version of users password
const hashed_password = await bcrypt.hash(password, salt);
const user = await User.create({ email, password: hashed_password });
if (user) {
res.status(201).json({ message: "new user created!" });
}
} catch (e) {
console.log(e);
}
};
No trecho de código acima, importamos
bcrypt
e nosso User
model, desestruturamos o email
do usuário e password
do req.body
objeto. Em seguida, fizemos o hash da senha usando bcrypt e criamos um novo usuário usando o sequelize create
método. Em seguida, crie uma
home page
, registration page
, login page
com o trecho de código abaixo:exports.HomePage = async (req, res) => {
if (!req.user) {
return res.redirect("/");
}
res.render("home", {
sessionID: req.sessionID,
sessionExpireTime: new Date(req.session.cookie.expires) - new Date(),
isAuthenticated: req.isAuthenticated(),
user: req.user,
});
};
exports.LoginPage = async (req, res) => {
res.render("auth/login");
};
exports.registerPage = async (req, res) => {
res.render("auth/register");
};
Na
HomePage
, renderizaremos alguns dos detalhes do usuário autenticado junto com o home
visualizar. Por fim, crie o
logout
route, para excluir os dados de nome de usuário do usuário com o snippet de código abaixo:exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Crie a estratégia do Passaporte
Nesse ponto, os usuários podem se registrar, fazer login e sair do nosso aplicativo. Agora, vamos criar a estratégia de passaporte para autenticar os usuários e criar uma sessão. Para fazer isso, abra o
src/utils/passport.js
arquivo e adicione o snippet de código abaixo:const LocalStrategy = require("passport-local/lib").Strategy;
const passport = require("passport");
const { User } = require("../models");
const bcrypt = require("bcrypt");
module.exports.passportConfig = () => {
passport.use(
new LocalStrategy(
{ usernameField: "email", passwordField: "password" },
async (email, password, done) => {
const user = await User.findOne({ where: { email: email } });
if (!user) {
return done(null, false, { message: "Invalid credentials.\n" });
}
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false, { message: "Invalid credentials.\n" });
}
return done(null, user);
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await User.findByPk(id);
if (!user) {
done(error, false);
}
done(null, user);
});
};
No trecho de código acima, importamos
passport
, bcrypt
, e nosso modelo User, e criamos um middleware de passaporte para usar a local-strategy
. Em seguida, renomeamos o nome do arquivo padrão para os nomes dos campos ( email
, password
) que estamos usando para autenticar os usuários. Agora, verificamos se os detalhes do usuário existem no banco de dados antes que uma sessão possa ser criada para eles. O
Passport.serialize
e passport.deserialize
Os comandos são usados para manter o ID do usuário como um cookie no navegador do usuário e para recuperar o ID do cookie quando necessário, que é usado para recuperar informações do usuário em um retorno de chamada. O
done()
função é um passport.js
interno função que usa o ID do usuário como o segundo parâmetro. Crie as rotas do aplicativo
Com nossa estratégia de passaporte criada, vamos prosseguir com a criação de rotas para nossos controladores. Para fazer isso, abra o
src/routes/index.js
arquivo e adicione o seguinte trecho de código abaixo:const express = require("express");
const {
Signup,
HomePage,
LoginPage,
registerPage,
Logout,
} = require("../controllers");
const passport = require("passport");
const router = express.Router();
router.route("/").get(LoginPage);
router.route("/register").get(registerPage);
router.route("/home").get(HomePage);
router.route("/api/v1/signin").post(
passport.authenticate("local", {
failureRedirect: "/",
successRedirect: "/home",
}),
function (req, res) {}
);
router.route("/api/v1/signup").post(Signup);
router.route("/logout").get(Logout);
module.exports = router;
No trecho de código acima, importamos nossas funções de controlador e criamos uma rota para elas. Para a
signin route
, usamos o passport.authenticate
método para autenticar os usuários usando o local
estratégia na configuração na seção anterior. Agora, de volta ao nosso
server.js
arquivo, criaremos um middleware para nossas rotas. Antes disso, precisamos importar nosso router
e o passportConfig
função. const router = require("./routes");
const { passportConfig } = require("./utils/passport");
Em seguida, chamaremos o
passportConfig
função logo abaixo do código nas áreas comentadas //Configure session middleware
. passportConfig();
Em seguida, criaremos nosso middleware de rota logo após a área comentada
//Router middleware
. app.use(router);
Crie nossas visualizações de aplicativos
Com nossas rotas criadas, criaremos visualizações renderizadas em nossa
HomePage
, LoginPage
e RegisterPage
controladores. Antes disso, vamos configurar nosso mecanismo de visualização ejs no arquivo server.js com um trecho de código abaixo logo abaixo da área comentada //app middleware
. app.set("view engine", "ejs");
Então, vamos começar com a página inicial, abra o
views/home.ejs
arquivo e adicione a seguinte marcação. <html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section>
<!-- As a heading -->
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand">Navbar</a>
<% if(isAuthenticated){ %>
<a href="/logout" class="btn btn-danger btn-md">Logout</a>
<% } %>
</div>
</nav>
<div class="">
<p class="center">
Welcome: <b><%= user.email %></b> your sessionID is <b><%= sessionID %></b>
</p>
<p>Your session expires in <b><%= sessionExpireTime %></b> seconds</p>
</div>
</section>
</body>
</html>
Aqui em nossa página inicial, usamos bootstrap para adicionar algum estilo às nossas marcações. Em seguida, verificamos se o usuário está autenticado para mostrar o botão de logout. Também exibimos o
Email
do usuário , sessionID
e ExpirationTime
do back-end. Em seguida, abra o
src/views/auth/resgister
e adicione a seguinte marcação abaixo para a página de registro. <html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section class="vh-100" style="background-color: #9a616d">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-xl-10">
<div class="card" style="border-radius: 1rem">
<div class="row g-0">
<div class="col-md-6 col-lg-5 d-none d-md-block">
<img
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
alt="login form"
class="img-fluid"
style="border-radius: 1rem 0 0 1rem"
/>
</div>
<div class="col-md-6 col-lg-7 d-flex align-items-center">
<div class="card-body p-4 p-lg-5 text-black">
<form action="api/v1/signup" method="post">
<h5
class="fw-normal mb-3 pb-3"
style="letter-spacing: 1px"
>
Signup into your account
</h5>
<div class="form-outline mb-4">
<input
name="email"
type="email"
id="form2Example17"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example17"
>Email address</label
>
</div>
<div class="form-outline mb-4">
<input
name="password"
type="password"
id="form2Example27"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example27"
>Password</label
>
</div>
<div class="pt-1 mb-4">
<button
class="btn btn-dark btn-lg btn-block"
type="submit"
>
Register
</button>
</div>
<a class="small text-muted" href="#!">Forgot password?</a>
<p class="mb-5 pb-lg-2" style="color: #393f81">
Don't have an account?
<a href="/" style="color: #393f81">Login here</a>
</p>
<a href="#!" class="small text-muted">Terms of use.</a>
<a href="#!" class="small text-muted">Privacy policy</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
Na página de cadastro, criamos um formulário html para aceitar os dados dos usuários. No formulário, também adicionamos o atributo ativo e especificamos o endpoint de inscrição. Isso significa que quando um usuário clicar no botão enviar, uma solicitação será enviada para o
/api/v1/signup
ponto final. Por fim, abra o
src/views/auth/signin.js
e adicione o seguinte snippet de marcação abaixo:<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section class="vh-100" style="background-color: #9a616d">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-xl-10">
<div class="card" style="border-radius: 1rem">
<div class="row g-0">
<div class="col-md-6 col-lg-5 d-none d-md-block">
<img
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
alt="login form"
class="img-fluid"
style="border-radius: 1rem 0 0 1rem"
/>
</div>
<div class="col-md-6 col-lg-7 d-flex align-items-center">
<div class="card-body p-4 p-lg-5 text-black">
<form action="api/v1/signin" method="post">
<h5
class="fw-normal mb-3 pb-3"
style="letter-spacing: 1px"
>
Sign into your account
</h5>
<div class="form-outline mb-4">
<input
name="email"
type="email"
id="form2Example17"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example17"
>Email address</label
>
</div>
<div class="form-outline mb-4">
<input
name="password"
type="password"
id="form2Example27"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example27"
>Password</label
>
</div>
<div class="pt-1 mb-4">
<button
class="btn btn-dark btn-lg btn-block"
type="submit"
>
Login
</button>
</div>
<a class="small text-muted" href="#!">Forgot password?</a>
<p class="mb-5 pb-lg-2" style="color: #393f81">
Don't have an account?
<a href="/register" style="color: #393f81"
>Register here</a
>
</p>
<a href="#!" class="small text-muted">Terms of use.</a>
<a href="#!" class="small text-muted">Privacy policy</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
Na marcação acima, adicionamos um formulário html que será usado no login de um usuário enviando uma solicitação para o
/api/v1/signin
ponto final. Visualize os dados dos usuários com Arctype
Agora criamos com sucesso um aplicativo de gerenciamento de sessão Node.js. Vejamos os dados dos usuários com Arctype. Para começar, inicie o Arctype, clique na guia MySQL e insira as seguintes credenciais do MySQL, conforme mostrado na captura de tela abaixo:
Em seguida, clique em
users
tabela para mostrar os usuários registrados como mostrado na captura de tela abaixo:Conclusão
Ao criar um aplicativo de login de demonstração, aprendemos como implementar o gerenciamento de sessão no Node.js usando o Passport e o Redis. Começamos com a introdução das sessões HTTP e como elas funcionam, depois analisamos o que é Redis e criamos um projeto para colocar tudo isso em prática. Agora que você tem o conhecimento que procura, como você autenticaria os projetos dos usuários?