Menu
CoddyTech

Cookie-Flags

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

Ein Cookie ohne Flags wird über einfaches HTTP übertragen, bei Anfragen mitgesendet, die andere Websites auslösen, und bleibt bestehen, bis der Browser geschlossen wird. Vier Optionen bei res.cookie schränken das ein:

  • httpOnly: Skripte auf der Seite können es nicht lesen.
  • sameSite: 'lax' verhindert, dass es bei Anfragen mitgesendet wird, die andere Websites senden, außer wenn der Benutzer einem Link folgt; 'strict' verhindert auch das.
  • secure: wird nur über HTTPS gesendet, niemals über einfaches HTTP, wo andere im Netzwerk es lesen können.
  • maxAge: wie lange es gültig ist, in Millisekunden.
res.cookie('cart', cartId, {
    httpOnly: true,
    sameSite: 'strict',
    secure: true,
    maxAge: 30 * 60 * 1000, // 30 Minuten
});
challenge icon

Aufgabe

Einfach

Schränke das sid-Cookie so ein, dass Browser es nur dort senden, wo es hingehört, und es nach einem Tag vergessen.

  1. Gib in server.js unter POST /login dem sid-Cookie diese vier Optionen:
    • httpOnly, damit Skripte auf der Seite es nicht lesen können. Diese Option ist bereits gesetzt.
    • Setze sameSite auf 'lax', damit der Browser es bei Anfragen, die von anderen Websites gestartet werden, nicht mitsendet, es sei denn, der Benutzer folgt einem Link zu deiner Website.
    • secure, damit es nur über HTTPS gesendet wird.
    • maxAge auf einen Tag. maxAge wird in Millisekunden angegeben, und der Header zeigt dieselbe Zeit in Sekunden an. Ein Tag erscheint daher als Max-Age=86400.
  2. Die Anmeldung muss weiterhin funktionieren: Nach POST /login gibt GET /me den Benutzer zurück.

Probiere es aus: Klicke auf „Ausführen“. Sende POST /signup und dann POST /login, jeweils mit {"username": "ada", "password": "correct horse"}. Öffne den Tab „Kopfzeilen“ der Anmeldeantwort. Set-Cookie sieht etwa so aus: sid=oqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN; Max-Age=86400; Path=/; Expires=Sat, 31 Oct 2026 12:00:00 GMT; HttpOnly; Secure; SameSite=Lax. Sende GET /me: {"username": "ada"}.

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

// Ermittelt den Benutzer anhand des Sitzungscookies 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 });
});

app.post('/logout', async (req, res) => {
    const { sid } = parseCookies(req.get('cookie'));
    if (sid) {
        await sessions.deleteOne({ _id: sid });
    }
    res.clearCookie('sid');
    res.status(204).end();
});

// 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');
});
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Authentifizierung und Sicherheit

Übe selbstständig: Express-Playground