Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como gerenciar sessões no Node.js usando Passport, Redis e MySQL


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?