Sessions de connexion
Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 10 sur 24.
La connexion reçoit une réponse 200, puis la requête suivante a oublié qui s’est connecté. Une session s’en souvient. Lors de la connexion, le serveur crée un identifiant impossible à deviner, enregistre à quel utilisateur il appartient et l’envoie sous forme de cookie. Chaque requête ultérieure renvoie l’identifiant, et le serveur le recherche.
L’identifiant doit être aléatoire. Un cookie contenant le nom d’utilisateur permettrait à n’importe qui de saisir Cookie: sid=ada et de se faire passer pour Ada. randomBytes(24) fournit 24 octets aléatoires, soit 192 bits, impossibles à deviner. toString('base64url') les écrit sous forme de 32 lettres, chiffres, - et _, des caractères qui peuvent être utilisés sans risque dans un cookie. Définis le cookie httpOnly afin que les scripts de la page ne puissent pas le lire.
const cartId = randomBytes(24).toString('base64url');
await carts.insertOne({ _id: cartId, items: [] });
res.cookie('cart', cartId, { httpOnly: true });
// lors d’une requête ultérieure
const cart = await carts.findOne({ _id: parseCookies(req.get('cookie')).cart });Défi
FacileCette étape ouvre le projet d’API de notes privées préparé pour le cours, plutôt que les fichiers de votre projet de Mémoriser avec les cookies (y compris server.js). Votre travail précédent reste dans sa propre leçon. Lisez les fichiers fournis avant de modifier server.js. Cette copie contient les routes d’inscription et de connexion des notes privées.
La connexion fonctionne, mais la requête suivante a déjà oublié qui s’est connecté. Faites en sorte que le serveur s’en souvienne au moyen d’une session : un identifiant aléatoire enregistré sur le serveur et envoyé au navigateur sous forme de cookie.
- Dans
server.js, importezrandomBytesdepuisnode:crypto. Importezsessionsdepuis./db.jsà côté denotesetusers. Le fichierdb.jsverrouillé contient déjà une collectionsessions. - Dans
POST /login, après la vérification du mot de passe, créez un identifiant de session : 24 octets aléatoires écrits sous forme de texte base64url. Cela représente 32 caractères, des lettres, des chiffres,-et_, commeoqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN. Créez un nouvel identifiant à chaque connexion. - Enregistrez la session dans
sessionssous la forme d’un document avec deux champs :_id: l’identifiant de session.userId: le_idde l’utilisateur connecté.
- Envoyez l’identifiant au navigateur sous la forme d’un cookie nommé
sid, avec l’optionhttpOnly, afin que les scripts de la page ne puissent pas le lire. La connexion répond toujours avec{"username": "ada"}. - Ajoutez
GET /me. Cette route lit le cookiesid, trouve la session avec cet_id, puis trouve l’utilisateur de cette session. Elle répond avec{"username": "ada"}. S’il n’y a pas de cookiesid, ou si aucune session ne possède cet identifiant, elle répond avec le statut 401 et{"error": "Log in first"}. Une personne qui saisit un cookie à la main, commesid=ada, doit recevoir ce statut 401.
Essayez : Appuyez sur « Jouer ». Envoyez POST /signup avec {"username": "ada", "password": "correct horse"}. Envoyez GET /me : statut 401 et {"error": "Log in first"}. Envoyez POST /login avec le même corps que pour l’inscription : statut 200. L’onglet Headers de la réponse affiche Set-Cookie: sid=...; Path=/; HttpOnly, avec votre identifiant aléatoire à la place des points. Envoyez de nouveau GET /me. Le navigateur renvoie le cookie, vous obtenez donc {"username": "ada"}. Ouvrez Data : sessions contient un document, avec votre identifiant comme _id et le _id d’ada comme userId.
Essayez vous-même
import express from 'express';
import { parseCookies } from './cookies.js';
import { notes, users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const app = express();
app.use(express.json());
app.post('/signup', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const password = String(req.body.password ?? '');
if (!username || password.length < 8) {
return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
}
if (await users.findOne({ username })) {
return res.status(409).json({ error: 'That username is taken' });
}
await users.insertOne({ username, passwordHash: await hashPassword(password) });
res.status(201).json({ username });
});
app.post('/login', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const user = await users.findOne({ username });
if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
return res.status(401).json({ error: 'Wrong username or password' });
}
res.json({ username: user.username });
});
// Compte le nombre de fois que ce navigateur a appelé GET /visit.
app.get('/visit', (req, res) => {
const cookies = parseCookies(req.get('cookie'));
const visits = Number(cookies.visits ?? 0) + 1;
res.cookie('visits', String(visits));
res.json({ visits });
});
// Notes. Pour l’instant, tout le monde peut les lire et en écrire.
app.get('/notes', async (req, res) => {
res.json(await notes.find().toArray());
});
app.post('/notes', async (req, res) => {
const note = { text: String(req.body.text ?? '') };
await notes.insertOne(note);
res.status(201).json(note);
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Toutes les leçons de Authentification et sécurité
2Sessions et cookies
Se souvenir avec les cookiesRecherche d’une sessionSessions de connexionRoutes protégées par une connexionEntraînez-vous par vous-même : Playground Express