Menu
CoddyTech

Abmelden

Teil des Abschnitts Authentifizierung und Sicherheit der Node.js-Journey von Coddy. Lektion 12 von 24.

Das Löschen des Cookies fordert den Browser nur auf, die ID zu vergessen. Wer die ID kopiert hat, von einem gemeinsam genutzten Computer oder aus einem gestohlenen Cookie, besitzt weiterhin einen funktionierenden Schlüssel. Beim Abmelden muss die Sitzung auf dem Server gelöscht werden.

res.clearCookie(name) weist den Browser an, ein Cookie zu löschen. Der Status 204 No Content bedeutet, dass es funktioniert hat und nichts zurückgesendet werden muss: res.status(204).end().

Hier ist eine zugehörige Route, die einen Benutzer gleichzeitig auf allen Geräten abmeldet:

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

Aufgabe

Einfach

Füge eine Möglichkeit zum Abmelden hinzu. Das Cookie zu löschen reicht allein nicht aus, denn jeder, der die Sitzungs-ID kopiert hat, könnte sie weiterhin verwenden. Deshalb vergisst der Server die Sitzung ebenfalls.

  1. Füge in server.js POST /logout hinzu. Es benötigt keinen Request-Body.
  2. Lies das sid-Cookie aus. Wenn eines vorhanden ist, lösche die Sitzung mit dieser _id aus sessions.
  3. Weise den Browser an, das sid-Cookie zu vergessen.
  4. Antworte mit Status 204 und ohne Body. 204 No Content bedeutet, dass es funktioniert hat und nichts zurückgesendet werden muss. Antworte auch dann mit 204, wenn der Request kein sid-Cookie enthielt.

Probiere es aus: Drücke auf „Spielen“. Sende POST /signup und danach POST /login, beide mit {"username": "ada", "password": "correct horse"}. GET /me gibt {"username": "ada"} zurück. Sende POST /logout: Status 204 und ein leerer Body. Sende erneut GET /me: Status 401 und {"error": "Log in first"}. Öffne „Data“: sessions ist leer. Sende noch einmal POST /logout: weiterhin 204.

Probier es selbst

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

// Findet den Benutzer des Session-Cookies oder antwortet mit 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 });
});

// Zählt, wie oft dieser Browser GET /visit aufgerufen hat.
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 });
});

// Notizen: Jeder Benutzer liest und schreibt nur seine eigenen.
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');
});

Alle Lektionen in Authentifizierung und Sicherheit

Übe selbstständig: Express-Playground