Menu
CoddyTech

Se déconnecter

Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 12 sur 24.

Effacer le cookie demande simplement au navigateur d’oublier l’identifiant. Toute personne qui a copié l’identifiant, depuis un ordinateur partagé ou un cookie volé, détient toujours une clé qui fonctionne. Pour se déconnecter, il faut supprimer la session sur le serveur.

res.clearCookie(name) indique au navigateur de supprimer un cookie. Le statut 204 No Content signifie que l’opération a réussi et qu’il n’y a rien à renvoyer : res.status(204).end().

Voici une route associée, qui déconnecte un utilisateur de tous ses appareils à la fois :

app.post('/logout-everywhere', requireUser, async (req, res) => {
    await sessions.deleteMany({ userId: new ObjectId(req.user.id) });
    res.clearCookie('sid');
    res.status(204).end();
});
challenge icon

Défi

Facile

Ajoute un moyen de se déconnecter. Supprimer le cookie ne suffit pas, car toute personne qui a copié l’identifiant de session pourrait continuer à l’utiliser. Le serveur oublie donc aussi la session.

  1. Dans server.js, ajoute POST /logout. Il n’a pas besoin de corps.
  2. Lis le cookie sid. S’il y en a un, supprime de sessions la session ayant cet _id.
  3. Dis au navigateur d’oublier le cookie sid.
  4. Réponds avec le statut 204 et sans corps. 204 No Content signifie que l’opération a réussi et qu’il n’y a rien à renvoyer. Réponds avec le statut 204 même si la requête ne contenait pas de cookie sid.

Essaie : Appuie sur « Jouer ». Envoie POST /signup, puis POST /login, les deux avec {"username": "ada", "password": "correct horse"}. GET /me renvoie {"username": "ada"}. Envoie POST /logout : statut 204 et corps vide. Envoie de nouveau GET /me : statut 401 et {"error": "Log in first"}. Ouvre Data : sessions est vide. Envoie encore une fois POST /logout : toujours 204.

Essayez vous-même

import express from 'express';
import { randomBytes } from 'node:crypto';
import { parseCookies } from './cookies.js';
import { notes, sessions, 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' });
    }
    const sid = randomBytes(24).toString('base64url');
    await sessions.insertOne({ _id: sid, userId: user._id, createdAt: new Date() });
    res.cookie('sid', sid, { httpOnly: true });
    res.json({ username: user.username });
});

// Trouve l'utilisateur associé au cookie de session, ou répond avec le code 401.
async function requireUser(req, res, next) {
    const { sid } = parseCookies(req.get('cookie'));
    const session = sid && (await sessions.findOne({ _id: sid }));
    const user = session && (await users.findOne({ _id: session.userId }));
    if (!user) {
        return res.status(401).json({ error: 'Log in first' });
    }
    req.user = { id: user._id.toHexString(), username: user.username };
    next();
}

app.get('/me', requireUser, (req, res) => {
    res.json({ username: req.user.username });
});

// Compte combien de fois 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 : chaque utilisateur ne lit et n'écrit que les siennes.
app.get('/notes', requireUser, async (req, res) => {
    res.json(await notes.find({ owner: req.user.id }).toArray());
});

app.post('/notes', requireUser, async (req, res) => {
    const note = { owner: req.user.id, 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