Menu
CoddyTech

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 });
challenge icon

Défi

Facile

Cette é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.

  1. Dans server.js, importez randomBytes depuis node:crypto. Importez sessions depuis ./db.js à côté de notes et users. Le fichier db.js verrouillé contient déjà une collection sessions.
  2. 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 _, comme oqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN. Créez un nouvel identifiant à chaque connexion.
  3. Enregistrez la session dans sessions sous la forme d’un document avec deux champs :
    • _id : l’identifiant de session.
    • userId : le _id de l’utilisateur connecté.
  4. Envoyez l’identifiant au navigateur sous la forme d’un cookie nommé sid, avec l’option httpOnly, afin que les scripts de la page ne puissent pas le lire. La connexion répond toujours avec {"username": "ada"}.
  5. Ajoutez GET /me. Cette route lit le cookie sid, 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 cookie sid, 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, comme sid=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é

Entraînez-vous par vous-même : Playground Express