HTTP et HTTPS sont des protocoles Internet qui permettent d'envoyer des données sur Internet en envoyant une requête via un navigateur Web. Parce qu'ils sont sans état, chaque requête envoyée au navigateur est traitée indépendamment. Cela signifie que le navigateur ne peut pas se souvenir de la source d'une requête, même si c'est le même utilisateur qui la fait. Les sessions HTTP résolvent ce problème.
Cet article examinera la gestion des sessions et comment des outils tels que Passport, Redis et MySQL peuvent nous aider à gérer les sessions Node.js. Plongeons-y.
Comment fonctionnent les sessions HTTP ?
Les sessions HTTP permettent aux serveurs Web de conserver l'identité de l'utilisateur et de stocker des données spécifiques à l'utilisateur à travers plusieurs interactions requête/réponse entre une application cliente et une application Web. Lorsqu'un client se connecte à l'application, le serveur génère un SessionID. La session est enregistrée en mémoire à l'aide d'un mécanisme de stockage persistant non répliqué sur un seul serveur. Des exemples de tels mécanismes incluent la persistance JDBC, la persistance du système de fichiers, la persistance de session basée sur les cookies et la réplication en mémoire. Lorsque l'utilisateur envoie une demande ultérieure, l'ID de session est transmis dans l'en-tête de la demande et le navigateur vérifie si l'ID correspond à l'un des éléments de la mémoire de stockage et accorde l'accès à l'utilisateur jusqu'à l'expiration de la session.
Les sessions HTTP stockent les données suivantes en mémoire :
- Spécificités de la session (identifiant de session, heure de création, heure du dernier accès, etc.)
- Informations contextuelles sur l'utilisateur (par exemple, l'état de connexion du client)
Qu'est-ce que Redis ?
Redis (Remote Dictionary Server) est un magasin de données clé-valeur rapide, open source et en mémoire utilisé comme base de données, cache, courtier de messages et file d'attente.
Redis a des temps de réponse inférieurs à la milliseconde, permettant des millions de requêtes par seconde pour des applications en temps réel dans des secteurs tels que les jeux, la technologie publicitaire, la finance, la santé et l'IoT. En conséquence, Redis est désormais l'un des moteurs open source les plus populaires, ayant été nommé la base de données "Most Loved" par Stack Overflow cinq années de suite. En raison de ses performances rapides, Redis est un choix populaire pour la mise en cache, la gestion de session, les jeux, les classements, l'analyse en temps réel, la géospatiale, le covoiturage, le chat/messagerie, le streaming multimédia et les pub/sous-applications.
Que construisons-nous ?
Pour démontrer la gestion de session dans Node.js, nous allons créer une application simple d'inscription et de connexion. Les utilisateurs s'inscriront et se connecteront à cette application en fournissant leur adresse e-mail et leur mot de passe. Une session est créée et enregistrée dans le magasin Redis pour les demandes futures lorsqu'un utilisateur se connecte. Lorsqu'un utilisateur se déconnecte, nous supprimons sa session. Assez parlé ; commençons !
Prérequis
Ce tutoriel est une démonstration pratique. Assurez-vous que les éléments suivants sont installés avant de commencer :
- Node.js
- CLI Redis
- Base de données MySQL
- Arctype
Le code de ce tutoriel est disponible sur mon dépôt Github. N'hésitez pas à cloner et à suivre.
Configuration du projet
Commençons par créer un dossier de projet pour l'application avec la commande ci-dessous :
mkdir Session_management && cd Session_management
Ensuite, initialisez une application Node.js pour créer un fichier package.json avec la commande ci-dessous :
npm init -y
Le -y
flag dans la commande ci-dessus indique à npm d'utiliser la configuration par défaut. Créez maintenant la structure de dossiers suivante dans le répertoire racine de votre projet.
Une fois notre package.json créé, installons le package requis pour ce projet dans la section suivante.
Installer les dépendances
Nous allons installer les dépendances suivantes pour notre application :
- Bcryptjs - Ce module sera utilisé pour hacher le mot de passe de l'utilisateur.
- Connect-redis - Ce module fournira un stockage de session Redis pour Express.
- Session express - Ce module sera utilisé pour créer des sessions.
- Ejs - Ce module est notre moteur de template
- Passeport - Ce module sera utilisé pour l'authentification de l'utilisateur
- Passeport local - Ce module sera utilisé pour l'authentification locale du nom d'utilisateur et du mot de passe
- Séqueliser - Ce module est notre ORM MySQL pour connecter notre application à la base de données MySQL.
- Dotenv - Ce module sera utilisé pour charger nos variables d'environnement.
Utilisez la commande ci-dessous pour installer toutes les dépendances requises.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Attendez que l'installation se termine. Une fois l'installation terminée, procédez à la configuration de la base de données MySQL dans la section suivante.
Configuration de la base de données MySQL
Nous allons créer une base de données MySQL pour notre application. Mais avant cela, exécutez la commande ci-dessous pour créer un compte utilisateur MySQL.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Créez maintenant une base de données session_db et accordez au nouvel utilisateur l'accès à la base de données avec la commande ci-dessous :
#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';
Rechargez maintenant tous les privilèges avec la commande ci-dessous :
FLUSH PRIVILEGES;
Avec notre configuration de base de données MySQL, créons nos users
modèle de base de données dans la section suivante.
Créer un serveur express
Avec notre configuration de base de données MySQL, créons un serveur express pour notre application. Ouvrez le fichier src/server.js et ajoutez l'extrait de code ci-dessous :
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}`);
});
Dans l'extrait de code ci-dessus, nous créons un serveur express, qui écoutera les requêtes sur le port 4300. Ensuite, nous analysons les requêtes entrantes avec des charges utiles JSON à l'aide de express.json()
intergiciel et analyse les requêtes entrantes avec urlencoded
en utilisant Express.urlencoded()
middleware.
Créer le modèle de base de données
À ce stade, notre serveur Express est configuré. Nous allons maintenant créer un Users
modèle pour représenter les données utilisateur, nous verrons la base de données en utilisant Sequelize
. Ouvrez le src/models/index.js
fichier et ajoutez l'extrait de code ci-dessous.
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,
},
});
Dans l'extrait de code ci-dessus, nous importons Sequelize
et DateTypes
de sequelize
pour se connecter à notre base de données MySQL et attribuer un type de données à nos propriétés de modèle. Ensuite, on se connecte à MySQL en créant un sequelize
instance de Sequelize
class et en transmettant nos informations d'identification de base de données. Par exemple, avec le sequelize
Par exemple, nous avons défini notre modèle et ses propriétés. Nous ne voulons que les champs d'identifiant, d'e-mail et de mot de passe de ce didacticiel. Mais sequelize crée deux champs supplémentaires, le createdAt
, et updatedAt
champs.
Configurer Passport et Redis
Pour gérer et stocker les identifiants de nos utilisateurs, nous utiliserons et configurerons Redis
. Pour ce faire, ouvrez le src/index.js
fichier et importez les dépendances suivantes ci-dessous :
const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
Ensuite, repérez la zone commentée //Redis configurations
et ajoutez l'extrait de code ci-dessous :
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
Dans l'extrait de code ci-dessus, nous avons établi une connexion à notre base de données, qui gérera les données de nom d'utilisateur de notre utilisateur.
Ensuite, repérez la zone commentée //Commented session middleware
et ajoutez l'extrait de code ci-dessous :
//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());
Dans l'extrait de code ci-dessus, nous avons créé un SESSION_SECRET
variable dans un .env
fichier pour garder notre session secrète, puis créé un middleware de session et utilisé Redis comme magasin. Pour que la session fonctionne, nous ajoutons deux middlewares supplémentaires, le passport.initialize()
, et passport.session()
.
Créer des contrôleurs d'application
Avec notre configuration de session Redis et express, nous allons créer un itinéraire pour gérer les informations des utilisateurs. Pour ce faire, ouvrez le src/controllers/index.js
fichier et ajoutez l'extrait de code ci-dessous :
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);
}
};
Dans l'extrait de code ci-dessus, nous importons bcrypt
et notre User
modèle, nous déstructurons l'email
de l'utilisateur et password
depuis le req.body
objet. Ensuite, nous avons haché le mot de passe en utilisant bcrypt et créons un nouvel utilisateur en utilisant le sequelize create
méthode.
Ensuite, créez une home page
, registration page
, login page
avec le bout de code ci-dessous :
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");
};
Dans la home page
, nous afficherons certains des détails de l'utilisateur authentifié à côté du home
vue.
Enfin, créez le logout
route, pour supprimer les données de nom d'utilisateur de l'utilisateur avec l'extrait de code ci-dessous :
exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Créer la stratégie Passeport
À ce stade, les utilisateurs peuvent s'inscrire, se connecter et se déconnecter de notre application. Créons maintenant la stratégie de passeport pour authentifier les utilisateurs et créer une session. Pour ce faire, ouvrez le src/utils/passport.js
fichier et ajoutez l'extrait de code ci-dessous :
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);
});
};
Dans l'extrait de code ci-dessus, nous importons passport
, bcrypt
, et notre modèle User, et nous créons un middleware de passeport pour utiliser la local-strategy
. Ensuite, nous renommons le nom de fichier par défaut en noms de champs ( email
, password
) que nous utilisons pour authentifier les utilisateurs. Maintenant, nous vérifions si les détails de l'utilisateur existent dans la base de données avant qu'une session puisse être créée pour eux.
Le Passport.serialize
et passport.deserialize
Les commandes sont utilisées pour conserver l'identifiant de l'utilisateur en tant que cookie dans le navigateur de l'utilisateur et pour récupérer l'identifiant du cookie si nécessaire, qui est ensuite utilisé pour récupérer les informations de l'utilisateur dans un rappel.
Le done()
la fonction est un passport.js
interne fonction qui prend l'identifiant de l'utilisateur comme second paramètre.
Créer les routes de l'application
Une fois notre stratégie de passeport créée, procédons à la création d'itinéraires pour nos contrôleurs. Pour ce faire, ouvrez le src/routes/index.js
fichier et ajoutez l'extrait de code suivant ci-dessous :
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;
Dans l'extrait de code ci-dessus, nous importons nos fonctions de contrôleur et créons une route pour elles. Pour la signin route
, nous avons utilisé le passport.authenticate
méthode pour authentifier les utilisateurs en utilisant le local
stratégie dans la configuration de la section précédente.
Revenons maintenant à notre server.js
fichier, nous allons créer un middleware pour nos routes. Avant cela, nous devons importer notre router
et le passportConfig
une fonction.
const router = require("./routes");
const { passportConfig } = require("./utils/passport");
Ensuite, nous appellerons le passportConfig
fonction juste en dessous du code dans les zones commentées //Configure session middleware
.
passportConfig();
Ensuite, nous créerons notre middleware de route juste après la zone commentée //Router middleware
.
app.use(router);
Créer nos vues d'application
Une fois nos routes créées, nous créerons des vues rendues dans notre HomePage
, LoginPage
, et RegisterPage
contrôleurs. Avant cela, nous allons configurer notre moteur de vue ejs dans le fichier server.js avec un extrait de code ci-dessous juste en dessous de la zone commentée //app middleware
.
app.set("view engine", "ejs");
Ensuite, nous allons commencer par la page d'accueil, ouvrir le views/home.ejs
fichier et ajoutez le balisage suivant.
<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>
Ici, sur notre page d'accueil, nous avons utilisé bootstrap pour ajouter du style à nos balises. Ensuite, nous vérifions si l'utilisateur est authentifié pour afficher le bouton de déconnexion. Nous affichons également l'Email
de l'utilisateur , sessionID
, et ExpirationTime
depuis le backend.
Ensuite, ouvrez le src/views/auth/resgister
et ajoutez le balisage suivant ci-dessous pour la page de registre.
<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>
Dans la page d'enregistrement, nous avons créé un formulaire html pour accepter les détails des utilisateurs. Dans le formulaire, nous ajoutons également l'attribut active et spécifions le point de terminaison d'inscription. Cela signifie que lorsqu'un utilisateur clique sur le bouton Soumettre, une demande sera envoyée à /api/v1/signup
point de terminaison.
Enfin, ouvrez le src/views/auth/signin.js
fichier et ajoutez l'extrait de balisage suivant ci-dessous :
<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>
Dans le balisage ci-dessus, nous avons ajouté un formulaire html qui sera utilisé pour connecter un utilisateur en envoyant une requête au /api/v1/signin
point de terminaison.
Afficher les données des utilisateurs avec Arctype
Nous avons maintenant créé avec succès une application de gestion de session Node.js. Regardons les données des utilisateurs avec Arctype. Pour commencer, lancez Arctype, cliquez sur l'onglet MySQL et entrez les informations d'identification MySQL suivantes, comme indiqué dans la capture d'écran ci-dessous :
Ensuite, cliquez sur les users
tableau pour afficher les utilisateurs enregistrés comme indiqué dans la capture d'écran ci-dessous :
Conclusion
En créant une application de connexion de démonstration, nous avons appris à implémenter la gestion de session dans Node.js à l'aide de Passport et Redis. Nous avons commencé par l'introduction des sessions HTTP et leur fonctionnement, puis nous avons regardé ce qu'est Redis, et créé un projet pour mettre tout cela en pratique. Maintenant que vous avez les connaissances recherchées, comment authentifierez-vous les projets des utilisateurs ?