Sichere JWT-Token in Next.js – Praktischer Leitfaden für Ingenieure

Erfahren Sie, wie Sie in Next.js und Node.js ein robustes JWT-System aufbauen, das Token vor Diebstahl, CSRF und XSS schützt.

Bezpieczne tokeny JWT w Next.js – praktyczny przewodnik dla inżynierów

Moderne Web‑Anwendungen basieren immer häufiger auf JWT‑Tokens (JSON Web Token) als grundlegenden Authentifizierungsmechanismus. In einer Next.js‑Umgebung, die Server‑Side‑Rendering (SSR) und Client‑Side‑Rendering (CSR) kombiniert, erfordert ein korrektes Sitzungs‑Management eine durchdachte Architektur. Dieser Artikel stellt das Threat‑Model vor, erklärt die Funktionsweise des Mechanismus, zeigt den Kontrast zwischen verwundbarem und verbessertem Code und liefert eine Checkliste zur Minderung, die den OWASP‑, CWE‑Standards und dem Prinzip des Least‑Privilege entspricht.

Threat‑Model – was kann schiefgehen?

Die grundlegenden Angriffsvektoren auf JWT‑basierte Systeme sind:

  • Diebstahl des Tokens durch XSS (ein in den Browser injizierter Skript greift auf das Token im lokalen Speicher oder auf ein ungeschütztes Cookie zu);
  • CSRF‑Angriff, wenn der Browser das Cookie mit dem Token automatisch in einer API‑Anfrage mitsendet;
  • Replay‑Attack – ein abgefangenes Token wird vor dem Ablauf erneut verwendet;
  • Fehlende Schlüsselrotation – bei einem Leak können alle Tokens auf einmal invalidiert werden.

Dieses Modell lässt sich als drei Schichten visualisieren: Token‑Speicherung, Token‑Verifizierung und Lebenszyklus‑Management. Jede Schicht erfordert eigene Sicherheitsmaßnahmen.

Wie funktioniert der JWT‑Mechanismus in Next.js?

Ein typischer Ablauf sieht folgendermaßen aus:

  1. Der Benutzer meldet sich an, der Server (Node.js) erzeugt zwei Tokens – accessToken (kurze Lebensdauer) und refreshToken (längere).
  2. Die Tokens werden in HttpOnly‑ und Secure‑Cookies an den Browser gesendet.
  3. Bei einer API‑Anfrage fügt der Browser automatisch das accessToken hinzu. Middleware in Next.js prüft Signatur und Ablaufdatum.
  4. Wenn das accessToken abläuft, ruft die Anwendung den Refresh‑Endpoint auf und übergibt das refreshToken. Der Server liefert ein neues accessToken und optional ein neues refreshToken zurück.

Entscheidend ist, dass die Tokens nicht im JavaScript verfügbar sind (daher HttpOnly) und dass jeder Endpoint die Signatur mit dem aktuellen Schlüssel verifizieren muss.

Beispiel: verwundbarer vs. verbesserter Code

Im Folgenden vergleichen wir zwei Code‑Snippets – zuerst eine einfache, aber unsichere Methode, das Token im localStorage zu speichern, und danach die empfohlene Implementierung mit HttpOnly‑Cookies und Middleware in Next.js.

// VULNERABLE – Speicherung im localStorage (anfällig für XSS)
function login(credentials) {
  fetch('/api/auth/login', {method: 'POST', body: JSON.stringify(credentials)})
    .then(r => r.json())
    .then(data => {
      localStorage.setItem('accessToken', data.accessToken);
      localStorage.setItem('refreshToken', data.refreshToken);
    });
}

// SECURE – Setzen von HttpOnly‑Cookies auf Serverseite
// pages/api/auth/login.js
import { sign } from 'jsonwebtoken';
export default async function handler(req, res) {
  const { username, password } = req.body;
  // ... credentials prüfen ...
  const accessToken = sign({ sub: username }, process.env.JWT_ACCESS_SECRET, { expiresIn: '15m' });
  const refreshToken = sign({ sub: username }, process.env.JWT_REFRESH_SECRET, { expiresIn: '30d' });
  res.setHeader('Set-Cookie', [
    `accessToken=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900`,
    `refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=2592000`
  ]);
  res.status(200).json({ message: 'Logged in' });
}

Im gesicherten Ansatz gelangen die Tokens nicht in JavaScript, und die Flags SameSite=Strict reduzieren das CSRF‑Risiko. Zusätzlich begrenzen wir den Path, sodass die Cookies nur an die dafür vorgesehenen Endpunkte gesendet werden.

Checklisten zur Minderung – was zu tun ist, um OWASP und CWE zu erfüllen

  • Speicherung: Verwende HttpOnly + Secure‑Cookies; vermeide localStorage und sessionStorage für Tokens.
  • Domain‑ und Pfad‑Einschränkung: Setze Domain und Path so, dass das Cookie nur für die API verfügbar ist.
  • SameSite: Wähle Strict oder Lax je nach Anforderung, um CSRF zu verhindern.
  • Validierung: Verifiziere Signatur, Ablaufdatum und Claims (iss, aud) serverseitig.
  • Schlüsselrotation: Ändere JWT‑Secrets regelmäßig; bewahre den vorherigen Schlüssel für einen kurzen Zeitraum auf, um die Validierung bestehender Tokens zu ermöglichen.
  • Token‑Erneuerung: Nutze kurze accessToken und längere refreshToken; nach erfolgreicher Erneuerung den vorherigen Refresh‑Token invalidieren.
  • Rate‑Limiting und Monitoring fehlgeschlagener Verifizierungen – reduziert das Risiko von Brute‑Force‑ und Token‑Replay‑Angriffen.

Die Implementierung der oben genannten Punkte entspricht CWE‑287 (Improper Authentication) und CWE‑352 (Cross‑Site Request Forgery).

Strategie zur JWT‑Schlüsselrotation

Schlüssel, die zum Signieren von Tokens verwendet werden, sollten eine festgelegte Gültigkeitsdauer haben (z. B. 30 Tage). Der Rotationsprozess umfasst:

  1. Erzeugen eines neuen Schlüssels und Speichern in einem sicheren Speicher (z. B. AWS KMS, HashiCorp Vault).
  2. Hinzufügen des neuen Schlüssels zur Liste der „akzeptierten“ Schlüssel in der Anwendungskonfiguration, wobei der vorherige Schlüssel als „Fallback“ erhalten bleibt.
  3. Nach Ablauf der Übergangsphase (z. B. 24 h) Entfernen des alten Schlüssels – alle mit dem älteren Schlüssel signierten Tokens werden nicht mehr akzeptiert.

In Next.js lässt sich das mit einer Hilfsfunktion umsetzen:

// utils/jwt.js
import jwt from 'jsonwebtoken';
const keys = {
  current: process.env.JWT_ACCESS_SECRET,
  previous: process.env.JWT_ACCESS_SECRET_OLD,
};
export function verify(token) {
  try {
    return jwt.verify(token, keys.current);
  } catch (e) {
    // fallback to previous key
    return jwt.verify(token, keys.previous);
  }
}

Dieser Ansatz erfüllt die OWASP‑Anforderung „Key Management“ und minimiert das Risiko von Sitzungsunterbrechungen während der Rotation.

Typische Fehler und Kompromisse

Even experienced teams make mistakes. Die häufigsten sind:

  • Festlegung einer zu langen Lebensdauer für Access‑Tokens – erhöht die Angriffsfläche bei einem Diebstahl.
  • Kein Refresh‑Token‑Renewal – ermöglicht die unbegrenzte Nutzung eines gestohlenen Tokens.
  • Verwendung eines einzigen Schlüssels für Access‑ und Refresh‑Tokens – erschwert die Rotation und erhöht die Folgen eines Lecks.
  • Ungeeignete SameSite‑Flags – bei None sind Tokens anfällig für CSRF im Cross‑Site‑Kontext.
  • Exponieren von Tokens in Logs – besonders in CI/CD‑Umgebungen.

Kompromisslösungen wie ein „sliding expiration“ für Refresh‑Tokens können helfen, sollten aber stets hinsichtlich Sicherheit und Usability bewertet werden.

Die beste Verteidigung ist nicht nur Technologie, sondern ein bewusstes Sitzungsdesign von Anfang an.

Praktische Implementierungs‑Checkliste

  • ✅ Verwende HttpOnly, Secure und SameSite=Strict beim Setzen von JWT‑Cookies.
  • ✅ Begrenze den Path der Cookies auf API‑Endpoints.
  • ✅ Implementiere Middleware in Next.js, die das Token bei jeder Anfrage verifiziert.
  • ✅ Erstelle einen Token‑Refresh‑Endpoint mit Rotation des Refresh‑Tokens.
  • ✅ Führe die Schlüsselrotation alle 30‑60 Tage durch und behalte den vorherigen Schlüssel als Fallback.
  • ✅ Überwache fehlgeschlagene Verifizierungen und setze Rate‑Limiting ein.
  • ✅ Prüfe den Code regelmäßig auf XSS und entferne alle gefährlichen Daten‑Einfügungen.

Durch die Erfüllung dieser Liste schafft das Engineering‑Team ein solides Fundament für Sitzungs‑Sicherheit, konform mit den OWASP Top 10‑Standards und CWE‑798 (Use of Hard‑coded Credentials).

Zusammenfassend erfordern sichere JWT‑Tokens in Next.js eine durchdachte Speicher‑Strategie, regelmäßige Schlüsselrotation und strenge Kontrolle über den Token‑Lebenszyklus. Durch die Umsetzung der beschriebenen Praktiken reduzieren Sie das Risiko von Token‑Diebstahl, CSRF‑ und XSS‑Angriffen und gewährleisten die Konformität mit den besten Branchenstandards. Wenn Sie Unterstützung bei einer Sicherheits‑Audit oder der Implementierung eines solchen Systems in Ihrem Unternehmen benötigen, kontaktieren Sie Coderia.it – wir helfen Ihnen, eine Lösung zu bauen, die höchste Sicherheits‑ und Leistungsanforderungen erfüllt.

Loslegen

Ein Projekt im Kopf?

Beschreiben Sie es in wenigen Sätzen. Ich antworte innerhalb von 24 Stunden mit Angebot und Stack-Vorschlag.