← Blog
·10 blog.minutes

Sichere Kodierungspraktiken fuer Webentwickler

Ein praktischer Leitfaden zur Sicherheit in Webanwendungen, der die haeufigsten Schwachstellen abdeckt - von XSS und SQL-Injection bis zur Dependency-Sicherheit und sicheren Authentifizierung.

Die meisten Sicherheitsluecken in Webanwendungen sind keine komplexen Zero-Day-Exploits. Sie sind die gleichen Fehler, die Entwickler seit Jahrzehnten machen: ungepruefte Eingaben, hartcodierte Secrets, fehlerhafte Sitzungsverwaltung und vergessene Debug-Endpunkte in der Produktion. Sicherheit ist keine separate Phase oder ein spezielles Wissen; sie ist eine Reihe von Praktiken, die in den taeglichen Entwicklungsablauf integriert werden muessen.

Dieser Leitfaden deckt die wichtigsten Sicherheitspraktiken fuer Webentwickler im Jahr 2026 ab. Er ist kein umfassendes Sicherheitshandbuch - dafuer gibt es OWASP. Er ist eine priorisierte Liste der Aenderungen, die die groesste Wirkung auf die Sicherheit Ihrer Anwendung haben, mit konkreten Codebeispielen und klaren Anleitungen.

XSS-Praevention: Der haeufigste Fehler

Cross-Site Scripting (XSS) bleibt die haeufigste Schwachstelle in Webanwendungen. Sie tritt auf, wenn eine Anwendung ungepruefte Benutzereingaben in die Seitenausgabe einfuegt. Ein Angreifer kann JavaScript in die Seite einschleusen, das dann im Kontext des Benutzers ausgefuehrt wird, Cookies stiehlt, Aktionen im Namen des Benutzers ausfuehrt oder den Inhalt der Seite aendert.

// Bad — directly injecting user input into HTML
document.getElementById("profile").innerHTML = userInput;

// Good — using textContent (no HTML parsing)
document.getElementById("profile").textContent = userInput;

// React — by default, JSX escapes all values
function Profile({ bio }: { bio: string }) {
  // bio is escaped automatically - no XSS here
  return <div>{bio}</div>;
}

// Dangerous — dangerouslySetInnerHTML bypasses escaping
function UnsafeProfile({ bio }: { bio: string }) {
  // Only use if you trust the HTML source completely
  return <div dangerouslySetInnerHTML={{ __html: sanitize(bio) }} />;
}

Moderne Frameworks (React, Vue, Svelte, Solid) escapen standardmaessig alle Ausgaben. Der haeufigste XSS-Vektor in Framework-basierten Anwendungen ist die Verwendung von dangerouslySetInnerHTML (React) oder v-html (Vue) mit nicht desinfizierten Daten. Wenn Sie HTML aus Benutzereingaben rendern muessen, verwenden Sie eine Desinfektionsbibliothek wie DOMPurify, bevor Sie es an den Browser weitergeben.

SQL-Injection: Immer noch relevant

SQL-Injection sollte im Jahr 2026 ein totes Problem sein, da ORMs und parametrisierte Abfragen weit verbreitet sind. Und doch taucht es immer wieder in Codebasen auf, in denen Entwickler Abfragen manuell zusammenbauen, in Legacy-Code oder wenn sie ORMs umgehen, um etwas zu tun, das 'einfach sein sollte'. Die Regel ist absolut: Bauen Sie Abfragen niemals durch String-Verkettung. Verwenden Sie immer parametrisierte Abfragen oder sicherheitsgepruefte Abfrage-Builder.

// Bad — string concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;
// If email is "' OR '1'='1", this returns every user

// Good — parameterized query
const query = "SELECT * FROM users WHERE email = $1";
const result = await db.query(query, [email]);

// ORMs — Prisma, Drizzle, Knex all use parameterized queries
const user = await prisma.user.findUnique({
  where: { email },
});

// Even with LIKE — use parameterization, not escaping
// Bad
const search = `SELECT * FROM products WHERE name LIKE '%${term}%'`;
// Good
const search = "SELECT * FROM products WHERE name LIKE $1";
const result = await db.query(search, [`%${term}%`]);

Parametrisierte Abfragen funktionieren, indem sie die SQL-Logik von den Daten trennen. Die Abfrage wird mit Platzhaltern ($1, $2 oder ? je nach Datenbank) geschrieben, und die Werte werden getrennt uebergeben. Die Datenbank stellt sicher, dass die Werte niemals als SQL-Code interpretiert werden, selbst wenn sie boesartige Zeichenfolgen enthalten. ORMs wie Prisma und Drizzle verwenden standardmaessig parametrisierte Abfragen.

Authentifizierung und Session-Management

Die Authentifizierung ist der am haeufigsten falsch implementierte Teil der Webentwicklung. Die naheliegendste Empfehlung ist: Schreiben Sie Ihre eigene Authentifizierung nicht von Grund auf. Verwenden Sie Auth0, Clerk, NextAuth.js, Lucia oder eine andere etablierte Bibliothek. Die Wahrscheinlichkeit, dass Ihre benutzerdefinierte Implementierung haeufige Fehler vermeidet, ist gering, und die Kosten fuer einen Authentifizierungsfehler sind katastrophal.

// Session best practices

// 1. Use httpOnly, Secure, SameSite cookies
export function setSessionCookie(token: string) {
  res.setHeader(
    "Set-Cookie",
    `session=${token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=${SEVEN_DAYS}`
  );
}

// 2. Rotate session IDs on privilege escalation
// After login, invalidate old session, create new one

// 3. Implement rate limiting on login endpoints
const limiter = rateLimit({
  window: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts per window
  key: (req) => req.ip || req.headers.get("x-forwarded-for") || "unknown",
});

// 4. Use constant-time comparison for secrets
// Don't use === — it leaks timing information
import { timingSafeEqual } from "crypto";
function verifyToken(token: string, expected: string): boolean {
  const buf1 = Buffer.from(token);
  const buf2 = Buffer.from(expected);
  if (buf1.length !== buf2.length) return false;
  return timingSafeEqual(buf1, buf2);
}

Session-Token sollten in httpOnly-Cookies gespeichert werden, die nicht von clientseitigem JavaScript gelesen werden koennen. Das Secure-Flag stellt sicher, dass das Cookie nur ueber HTTPS gesendet wird. SameSite=Lax verhindert, dass das Cookie bei Cross-Site-Anfragen gesendet wird, und blockiert CSRF-Angriffe. Token-Rotation bei Berechtigungserhoehung (z. B. Login) verhindert Session-Fixation.

Dependency-Sicherheit

Ihre Anwendung ist nur so sicher wie ihre am wenigsten sichere Abhaengigkeit. Moderne Webanwendungen haben haeufig Hunderte oder Tausende von Abhaengigkeiten, von denen viele von Drittanbietern verwaltet werden. Jede Abhaengigkeit ist ein potenzieller Angriffsvektor. Das npm-Oekosystem hat eine Geschichte von Malware, die als legitime Pakete getarnt ist, und Abhaengigkeitsverwechslungsangriffen.

// package.json — audit scripts check for vulnerabilities
{
  "scripts": {
    "audit": "npm audit --audit-level=high",
    "outdated": "npm outdated",
    "preinstall": "npx lockfile-lint --path package-lock.json --validate-https"
  }
}

// CI pipeline — fail on high/critical vulnerabilities
- name: dependency-scan
  run: pnpm audit --audit-level=high

// Use tools to detect malicious packages
// - Socket.dev (real-time package risk scoring)
// - Snyk (vulnerability scanning)
// - npm audit (basic scanning)

// Best practice: pin exact versions in production
// "express": "4.18.2" not "express": "^4.18.0"

Fuehren Sie regulaer Abhaengigkeits-Scans in Ihrer CI/CD-Pipeline durch. npm audit, Snyk und Socket.dev erkennen bekannte Schwachstellen und bösartige Pakete. Wenn ein Scan einen kritischen Fehler meldet, behandeln Sie ihn wie einen Build-Fehler: Blockieren Sie die Pipeline, bis er behoben ist. Aktualisieren Sie Abhaengigkeiten regelmaessig, aber ueberpruefen Sie Aenderungen in neuen Versionen, bevor Sie sie in Produktion uebernehmen.

Geheimnisverwaltung

Hartcodierte API-Schluessel und Datenbankkennwoerter sind eine der haeufigsten Ursachen fuer Sicherheitsverletzungen. Ein Entwickler committed versehentlich einen API-Schluessel, der Bot scannt das Repository, und innerhalb von Minuten wird der Schluessel verwendet, um auf Ihre Produktionsressourcen zuzugreifen. Die Loesung ist einfach: Speichern Sie niemals Secrets im Code oder in Konfigurationsdateien, die in das Repository eingecheckt werden.

// Bad — hardcoded in source
const API_KEY = "sk-live-abc123";

// Good — environment variables loaded at runtime
const API_KEY = process.env.STRIPE_API_KEY;
if (!API_KEY) throw new Error("STRIPE_API_KEY is required");

// Better — secret management service
// Use AWS Secrets Manager, HashiCorp Vault, or Doppler
const stripeKey = await secrets.get("stripe/live/api_key");

// CI/CD — inject secrets at deploy time, not at build time
// Build: process.env.NEXT_PUBLIC_STRIPE_KEY is not available
// Deploy: Kubernetes secret injection, Vercel env vars, etc.

// Pre-commit check — prevent secrets from being committed
# .git/hooks/pre-commit or use tools like git-secrets
for file in $(git diff --cached --name-only); do
  if grep -qE "(sk_live_|pk_live_|AKIA)" "$file"; then
    echo "Secret found in $file"
    exit 1
  fi
done

Verwenden Sie einen Secrets-Manager fuer Ihre Produktionsumgebung. AWS Secrets Manager, HashiCorp Vault, Doppler und GitHub Secrets ermoeglichen es, Secrets unabhaengig vom Code zu speichern und zu rotieren. Lokale Entwicklung sollte .env-Dateien verwenden, die explizit in .gitignore aufgenommen werden. Pre-Commit-Hooks koennen versehentlich committed Secrets erkennen.

Sicherheit ist eine Gewohnheit, kein Projekt

Die sichersten Teams sind nicht diejenigen, die einmal im Jahr ein Sicherheitsaudit durchfuehren. Es sind diejenigen, die Sicherheit in ihren taeglichen Arbeitsablauf integriert haben. Pre-Commit-Hooks, die Secrets scannen. CI-Stufen, die auf Schwachstellen prufen. Code-Reviews, die nach XSS-Vektoren suchen. Automatisierte Dependency-Updates. Das sind Gewohnheiten, nicht Projekte. Sie muessen nicht perfekt sein - Sie muessen nur konsistent besser sein.

Fangen Sie mit den Aenderungen an, die die groesste Wirkung haben: Schalten Sie Sicherheits-Header in Ihrem Webserver ein, fuegen Sie Abhaengigkeits-Scans zu Ihrer CI-Pipeline hinzu, stellen Sie sicher, dass alle Cookies httpOnly und secure sind, und hoeren Sie auf, Abfragen per String-Verkettung zu bauen. Jede dieser Aenderungen dauert weniger als einen Tag und reduziert Ihr Risiko erheblich.