Menu
CoddyTech

Anmeldesitzungen

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

Die Anmeldung antwortet mit 200, aber die nächste Anfrage hat vergessen, wer sich angemeldet hat. Eine Sitzung merkt sich das. Bei der Anmeldung erstellt der Server eine ID, die niemand erraten kann, speichert, zu welchem Benutzer sie gehört, und sendet sie als Cookie. Jede spätere Anfrage bringt die ID zurück, und der Server sucht sie nach.

Die ID muss zufällig sein. Ein Cookie mit dem Benutzernamen würde es jedem ermöglichen, Cookie: sid=ada einzugeben und Ada zu sein. randomBytes(24) liefert 24 zufällige Bytes, also 192 Bits, die niemand erraten kann. toString('base64url') schreibt sie als 32 Buchstaben, Ziffern, - und _ – Zeichen, die in einem Cookie sicher sind. Setze das Cookie auf httpOnly, damit Skripte auf der Seite es nicht lesen können.

const cartId = randomBytes(24).toString('base64url');
await carts.insertOne({ _id: cartId, items: [] });
res.cookie('cart', cartId, { httpOnly: true });

// bei einer späteren Anfrage
const cart = await carts.findOne({ _id: parseCookies(req.get('cookie')).cart });
challenge icon

Aufgabe

Einfach

Dieser Schritt öffnet das vorbereitete Projekt für eine private Notizen-API des Kurses, nicht deine Projektdateien aus Remembering with Cookies (einschließlich server.js). Deine frühere Arbeit bleibt in ihrer eigenen Lektion. Lies die bereitgestellten Dateien, bevor du server.js bearbeitest. Diese Kopie enthält die Routen für die Registrierung und Anmeldung bei privaten Notizen.

Die Anmeldung funktioniert, aber die nächste Anfrage hat bereits vergessen, wer sich angemeldet hat. Sorge dafür, dass sich der Server dies mithilfe einer Sitzung merkt: einer zufälligen ID, die auf dem Server gespeichert und als Cookie an den Browser gesendet wird.

  1. Importiere in server.js randomBytes aus node:crypto. Importiere sessions neben notes und users aus ./db.js. Die gesperrte Datei db.js enthält bereits eine sessions-Collection.
  2. Erzeuge in POST /login nach der Überprüfung des Passworts eine Sitzungs-ID: 24 zufällige Bytes, die als base64url-Text ausgegeben werden. Das sind 32 Zeichen aus Buchstaben, Ziffern, - und _, wie oqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN. Erzeuge bei jeder Anmeldung eine neue ID.
  3. Speichere die Sitzung in sessions als Dokument mit zwei Feldern:
    • _id: die Sitzungs-ID.
    • userId: die _id des angemeldeten Benutzers.
  4. Sende die ID als Cookie namens sid an den Browser, mit der Option httpOnly, damit Skripte auf der Seite sie nicht lesen können. Die Anmeldung antwortet weiterhin mit {"username": "ada"}.
  5. Füge GET /me hinzu. Lies das sid-Cookie aus, suche die Sitzung mit dieser _id und suche anschließend den Benutzer dieser Sitzung. Die Antwort lautet {"username": "ada"}. Wenn kein sid-Cookie vorhanden ist oder keine Sitzung diese ID hat, antworte mit dem Status 401 und {"error": "Log in first"}. Wer ein Cookie von Hand eingibt, zum Beispiel sid=ada, muss diesen Status 401 erhalten.

Probiere es aus: Drücke auf „Ausführen“. Sende POST /signup mit {"username": "ada", "password": "correct horse"}. Sende GET /me: Status 401 und {"error": "Log in first"}. Sende POST /login mit demselben Inhalt wie bei der Registrierung: Status 200. Im Tab „Headers“ der Antwort wird Set-Cookie: sid=...; Path=/; HttpOnly angezeigt, wobei deine zufällige ID anstelle der Punkte steht. Sende erneut GET /me. Der Browser sendet das Cookie zurück, sodass du {"username": "ada"} erhältst. Öffne „Data“: sessions enthält ein Dokument, mit deiner ID als _id und Adas _id als userId.

Probier es selbst

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

// 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. Vorerst kann jeder sie lesen und schreiben.
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');
});

Alle Lektionen in Authentifizierung und Sicherheit

Übe selbstständig: Express-Playground