Express.js mit Sicherheit ab Werk

REST-API, Anmeldung mit zweitem Faktor, Passwort-Reset per E-Mail, Kontoverwaltung und Benutzerverwaltung in einem Projekt. Die Grundlagen sind abgesichert und getestet, dein Code beginnt bei der eigentlichen Aufgabe.

Diese Antwort kam mit 10 Sicherheits-Headern
  • content-security-policy default-src 'self';base-uri 'none';script-src 'self';script-src-attr 'none';style-src 'self';img-src 'self';font-src 'self';connect-src 'self';manifest-src 'self';media-src 'none';worker-src 'none';form-action 'self';frame-ancestors 'none';object-src 'none'
  • strict-transport-security max-age=63072000; includeSubDomains; preload
  • cross-origin-opener-policy same-origin
  • cross-origin-embedder-policy require-corp
  • cross-origin-resource-policy same-origin
  • x-frame-options DENY
  • x-content-type-options nosniff
  • referrer-policy no-referrer
  • permissions-policy accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()
  • cache-control no-store

Der Weg einer Anfrage

Bevor dein Code eine Anfrage sieht, läuft sie durch sieben Stationen. Scroll mit.

  1. 01

    Ankunft

    Jede Anfrage bekommt eine ID, die in jedem Log-Eintrag und auf Fehlerseiten auftaucht. Was größer als 100 KB ist, lehnt der Server ab, bevor er es liest.

    X-Request-Id: 7c1e…
  2. 02

    Header

    Content Security Policy ohne Inline-Skripte, HSTS, COOP, COEP und eine Permissions-Policy, die Kamera, Mikrofon und Standort sperrt.

    Content-Security-Policy: default-src 'self'
  3. 03

    Tempolimit

    300 Anfragen pro 15 Minuten und IP, bei Anmeldung und Passwortformularen 10 Fehlversuche. Mit RATE_LIMIT_STORE=database teilen sich mehrere Instanzen die Zähler.

    RateLimit-Policy: 300;w=900
  4. 04

    Herkunft

    Formulare tragen ein CSRF-Token, das bei jeder Ausgabe neu maskiert wird. Posts von fremden Seiten scheitern schon an der Fetch-Metadata-Prüfung.

    Sec-Fetch-Site: same-origin
  5. 05

    Eingaben

    Joi prüft jedes Feld gegen ein Schema und lehnt unbekannte Felder ab. MongoDB-Operatoren und Prototype-Schlüssel werden vorher entfernt.

    400 "email" must be a valid email
  6. 06

    Identität

    Session-Cookie oder JWT, die Rolle kommt aus der Datenbank, auf Wunsch mit zweitem Faktor. Nach einer Passwortänderung sind alte Tokens und Sessions ungültig.

    Authorization: Bearer eyJhbGciOiJIUzI1NiJ9…
  7. 07

    Antwort

    Fehler verraten keine Interna, nur eine Fehler-ID. Laufzeit und Status landen als Metrik für Prometheus.

    200 OK

Was drin ist

API und Browser

JSON-API unter /v1 mit Access- und Refresh-Tokens, dazu serverseitig gerenderte Seiten mit Session-Anmeldung. OpenAPI-Doku unter /docs.

Vollständige Anmeldung

Registrierung, E-Mail-Bestätigung, Passwort vergessen, Zwei-Faktor mit Authenticator-App und Wiederherstellungscodes.

Konto in Nutzerhand

Profil, Passwort, angemeldete Geräte, Aktivitätsprotokoll, Datenexport und Löschung, wie es die DSGVO verlangt.

Verwaltung

Benutzer suchen, Rollen vergeben und Konten löschen, im Browser und über die API mit denselben Rechten.

Zwei Sprachen

Oberfläche und E-Mails auf Deutsch und Englisch. Weitere Sprachen sind eine Datei mehr.

Bereit für den Betrieb

Docker, Health-Checks, Prometheus-Metriken, strukturierte Logs mit Request-ID und ein Lasttest für jeden Endpunkt.

In drei Befehlen lokal

Node.js 22 reicht. Die Daten landen in SQLite, bis DATABASE_URL auf MongoDB, PostgreSQL, MySQL oder einen anderen Server zeigt. Die Einrichtung erzeugt eine .env mit zufälligen Schlüsseln.

  1. npm run setup.env mit eigenen Schlüsseln anlegen
  2. npm run create-adminErstes Administratorkonto erstellen
  3. npm run devServer mit Neustart bei Änderungen starten