ToolBox Hub

API-Sicherheit Best Practices 2026

API-Sicherheit Best Practices 2026

Umfassender Guide zur Sicherheit von REST- und GraphQL-APIs. Authentifizierung, Autorisierung, Rate Limiting.

16. März 202610 Min. Lesezeit

Einleitung: Warum API-Sicherheit kritisch ist

APIs (Application Programming Interfaces) bilden das Rückgrat moderner Webanwendungen. Im Jahr 2026 kommuniziert praktisch jede Anwendung über APIs -- ob mobile Apps, Single-Page-Applications, Microservices oder IoT-Geräte. Doch mit der zunehmenden Verbreitung von APIs wächst auch die Angriffsfläche. Laut aktuellen Sicherheitsberichten sind API-bezogene Angriffe der am schnellsten wachsende Angriffsvektor, wobei über 90% der Webanwendungen mindestens eine API-Sicherheitslücke aufweisen.

Dieser umfassende Guide behandelt die wichtigsten Best Practices für die Absicherung von REST- und GraphQL-APIs. Von Authentifizierung über Autorisierung bis hin zu Rate Limiting und Monitoring -- jede Technik wird mit praktischen Codebeispielen und klaren Handlungsanweisungen dargestellt.

OWASP API Security Top 10 (2026)

Die OWASP (Open Web Application Security Project) pflegt eine regelmäßig aktualisierte Liste der kritischsten API-Sicherheitsrisiken. Hier sind die wichtigsten Kategorien:

  1. Broken Object Level Authorization (BOLA) -- Der häufigste API-Angriff. Angreifer manipulieren Objekt-IDs, um auf Daten anderer Nutzer zuzugreifen.
  2. Broken Authentication -- Schwache oder fehlerhafte Authentifizierungsmechanismen.
  3. Broken Object Property Level Authorization -- Übermäßige Datenexposition oder Mass Assignment.
  4. Unrestricted Resource Consumption -- Fehlende Rate Limits und Ressourcenbegrenzungen.
  5. Broken Function Level Authorization -- Fehlende Berechtigungsprüfungen für Admin-Funktionen.
  6. Unrestricted Access to Sensitive Business Flows -- Automatisierter Missbrauch von Geschäftslogik.
  7. Server-Side Request Forgery (SSRF) -- Server wird als Proxy für interne Anfragen missbraucht.
  8. Security Misconfiguration -- Fehlerhafte Sicherheitskonfiguration.
  9. Improper Inventory Management -- Veraltete oder undokumentierte API-Endpunkte.
  10. Unsafe Consumption of APIs -- Ungeprüftes Vertrauen in Drittanbieter-APIs.

Authentifizierung richtig implementieren

JWT (JSON Web Tokens) sicher einsetzen

JWTs sind der am häufigsten verwendete Authentifizierungsmechanismus für APIs. Doch falsch eingesetzt, können sie ein erhebliches Sicherheitsrisiko darstellen.

import jwt from 'jsonwebtoken';
import crypto from 'crypto';

// SCHLECHT: Symmetrischer Schlüssel, zu langer Ablauf
const token = jwt.sign(
  { userId: '123', role: 'admin' },
  'mein-geheimes-passwort',  // Schwacher Schlüssel!
  { expiresIn: '30d' }       // Zu lang!
);

// GUT: Asymmetrische Signierung, kurze Lebensdauer
const privateKey = crypto.createPrivateKey({
  key: process.env.JWT_PRIVATE_KEY!,
  format: 'pem',
});

interface TokenPayload {
  sub: string;        // Benutzer-ID
  rolle: string;
  berechtigungen: string[];
  jti: string;        // Eindeutige Token-ID (für Widerruf)
}

function erstelleAccessToken(benutzer: Benutzer): string {
  const payload: TokenPayload = {
    sub: benutzer.id,
    rolle: benutzer.rolle,
    berechtigungen: benutzer.berechtigungen,
    jti: crypto.randomUUID(),
  };

  return jwt.sign(payload, privateKey, {
    algorithm: 'RS256',      // Asymmetrisch
    expiresIn: '15m',        // Kurze Lebensdauer
    issuer: 'api.meine-app.de',
    audience: 'meine-app.de',
  });
}

function erstelleRefreshToken(benutzerId: string): string {
  return jwt.sign(
    { sub: benutzerId, jti: crypto.randomUUID() },
    privateKey,
    {
      algorithm: 'RS256',
      expiresIn: '7d',
    }
  );
}

JWT Best Practices Checkliste

  • Verwenden Sie RS256 oder ES256 statt HS256 für produktive Systeme
  • Setzen Sie kurze Ablaufzeiten (15 Minuten für Access Tokens)
  • Implementieren Sie Refresh Token Rotation -- jeder Refresh erzeugt ein neues Token-Paar
  • Speichern Sie Refresh Tokens nur in HTTP-only Cookies mit Secure- und SameSite-Flag
  • Fügen Sie eine eindeutige Token-ID (jti) für Token-Widerruf hinzu
  • Validieren Sie alle Claims: iss, aud, exp, nbf
  • Speichern Sie keine sensiblen Daten im Token-Payload

OAuth 2.0 und OpenID Connect

// OAuth 2.0 Authorization Code Flow mit PKCE
// (Empfohlen für SPAs und Mobile Apps)

class AuthService {
  private readonly clientId = process.env.OAUTH_CLIENT_ID!;
  private readonly redirectUri = 'https://meine-app.de/callback';
  private readonly authServer = 'https://auth.meine-app.de';

  // Schritt 1: Autorisierungs-URL generieren
  async starteAutorisierung(): Promise<string> {
    // PKCE: Code Verifier und Challenge generieren
    const codeVerifier = this.generiereZufallsString(128);
    const codeChallenge = await this.erstelleCodeChallenge(codeVerifier);
    const state = this.generiereZufallsString(32);

    // Code Verifier und State speichern
    sessionStorage.setItem('pkce_verifier', codeVerifier);
    sessionStorage.setItem('oauth_state', state);

    const params = new URLSearchParams({
      response_type: 'code',
      client_id: this.clientId,
      redirect_uri: this.redirectUri,
      scope: 'openid profile email',
      state: state,
      code_challenge: codeChallenge,
      code_challenge_method: 'S256',
    });

    return `${this.authServer}/authorize?${params}`;
  }

  // Schritt 2: Authorization Code gegen Token tauschen
  async behandleCallback(code: string, state: string): Promise<TokenAntwort> {
    const gespeicherterState = sessionStorage.getItem('oauth_state');
    if (state !== gespeicherterState) {
      throw new Error('State-Mismatch -- möglicher CSRF-Angriff');
    }

    const codeVerifier = sessionStorage.getItem('pkce_verifier')!;

    const antwort = await fetch(`${this.authServer}/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: this.clientId,
        code: code,
        redirect_uri: this.redirectUri,
        code_verifier: codeVerifier,
      }),
    });

    return antwort.json();
  }

  private async erstelleCodeChallenge(verifier: string): Promise<string> {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const hash = await crypto.subtle.digest('SHA-256', data);
    return btoa(String.fromCharCode(...new Uint8Array(hash)))
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
  }

  private generiereZufallsString(laenge: number): string {
    const array = new Uint8Array(laenge);
    crypto.getRandomValues(array);
    return Array.from(array, b => b.toString(36)).join('').slice(0, laenge);
  }
}

Autorisierung: Wer darf was?

Role-Based Access Control (RBAC)

// Rollen und Berechtigungen definieren
const BERECHTIGUNGEN = {
  BENUTZER_LESEN: 'benutzer:lesen',
  BENUTZER_ERSTELLEN: 'benutzer:erstellen',
  BENUTZER_AENDERN: 'benutzer:aendern',
  BENUTZER_LOESCHEN: 'benutzer:loeschen',
  ARTIKEL_LESEN: 'artikel:lesen',
  ARTIKEL_ERSTELLEN: 'artikel:erstellen',
  ARTIKEL_VEROEFFENTLICHEN: 'artikel:veroeffentlichen',
  SYSTEM_ADMIN: 'system:admin',
} as const;

const ROLLEN = {
  LESER: [BERECHTIGUNGEN.BENUTZER_LESEN, BERECHTIGUNGEN.ARTIKEL_LESEN],
  AUTOR: [
    BERECHTIGUNGEN.BENUTZER_LESEN,
    BERECHTIGUNGEN.ARTIKEL_LESEN,
    BERECHTIGUNGEN.ARTIKEL_ERSTELLEN,
  ],
  REDAKTEUR: [
    BERECHTIGUNGEN.BENUTZER_LESEN,
    BERECHTIGUNGEN.ARTIKEL_LESEN,
    BERECHTIGUNGEN.ARTIKEL_ERSTELLEN,
    BERECHTIGUNGEN.ARTIKEL_VEROEFFENTLICHEN,
  ],
  ADMIN: Object.values(BERECHTIGUNGEN),
} as const;

// Middleware für Berechtigungsprüfung
function erfordertBerechtigung(...erforderlich: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const benutzer = req.benutzer;

    if (!benutzer) {
      return res.status(401).json({ fehler: 'Nicht authentifiziert' });
    }

    const hatBerechtigung = erforderlich.every(berechtigung =>
      benutzer.berechtigungen.includes(berechtigung)
    );

    if (!hatBerechtigung) {
      return res.status(403).json({ fehler: 'Keine Berechtigung' });
    }

    next();
  };
}

// Verwendung
app.delete(
  '/api/benutzer/:id',
  authentifiziere(),
  erfordertBerechtigung(BERECHTIGUNGEN.BENUTZER_LOESCHEN),
  loescheBenutzer
);

Object-Level Authorization (BOLA-Schutz)

Der häufigste API-Angriff ist das Manipulieren von Objekt-IDs. Schützen Sie sich dagegen:

// SCHLECHT: Keine Prüfung, ob der Benutzer auf diese Bestellung zugreifen darf
app.get('/api/bestellungen/:id', async (req, res) => {
  const bestellung = await db.bestellungen.findById(req.params.id);
  res.json(bestellung); // Jeder kann jede Bestellung sehen!
});

// GUT: Explizite Autorisierungsprüfung
app.get('/api/bestellungen/:id', authentifiziere(), async (req, res) => {
  const bestellung = await db.bestellungen.findById(req.params.id);

  if (!bestellung) {
    return res.status(404).json({ fehler: 'Bestellung nicht gefunden' });
  }

  // Prüfen ob der Benutzer Zugriff auf diese Bestellung hat
  if (bestellung.benutzerId !== req.benutzer.id &&
      !req.benutzer.berechtigungen.includes('bestellungen:alle_lesen')) {
    return res.status(403).json({ fehler: 'Kein Zugriff auf diese Bestellung' });
  }

  res.json(bestellung);
});

Rate Limiting und Throttling

Rate Limiting implementieren

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

// Verschiedene Rate Limits für verschiedene Endpunkte
const rateLimits = {
  // Standard-API: 100 Anfragen pro Minute
  standard: new Ratelimit({
    redis: Redis.fromEnv(),
    limiter: Ratelimit.slidingWindow(100, '1m'),
    prefix: 'rl:standard',
  }),

  // Authentifizierung: 5 Versuche pro 15 Minuten
  auth: new Ratelimit({
    redis: Redis.fromEnv(),
    limiter: Ratelimit.slidingWindow(5, '15m'),
    prefix: 'rl:auth',
  }),

  // API-Schlüssel: 1000 Anfragen pro Minute
  apiKey: new Ratelimit({
    redis: Redis.fromEnv(),
    limiter: Ratelimit.slidingWindow(1000, '1m'),
    prefix: 'rl:api',
  }),
};

// Middleware
async function rateLimitMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const identifikator = req.benutzer?.id || req.ip;
  const limitTyp = req.path.startsWith('/api/auth') ? 'auth' : 'standard';

  const { success, remaining, reset } = await rateLimits[limitTyp].limit(
    identifikator
  );

  // Rate-Limit-Header setzen
  res.setHeader('X-RateLimit-Remaining', remaining);
  res.setHeader('X-RateLimit-Reset', reset);
  res.setHeader('Retry-After', Math.ceil((reset - Date.now()) / 1000));

  if (!success) {
    return res.status(429).json({
      fehler: 'Zu viele Anfragen',
      nachricht: 'Bitte versuchen Sie es später erneut',
      wiederholenNach: Math.ceil((reset - Date.now()) / 1000),
    });
  }

  next();
}

Eingabevalidierung und Sanitisierung

Request-Validierung mit Zod

import { z } from 'zod';

// Schema für Benutzerregistrierung
const RegistrierungsSchema = z.object({
  name: z
    .string()
    .min(2, 'Name muss mindestens 2 Zeichen lang sein')
    .max(100, 'Name darf maximal 100 Zeichen lang sein')
    .regex(/^[a-zA-ZäöüÄÖÜß\s-]+$/, 'Name enthält ungültige Zeichen'),
  email: z
    .string()
    .email('Ungültige E-Mail-Adresse')
    .toLowerCase()
    .max(254),
  passwort: z
    .string()
    .min(12, 'Passwort muss mindestens 12 Zeichen lang sein')
    .regex(/[A-Z]/, 'Passwort muss mindestens einen Großbuchstaben enthalten')
    .regex(/[a-z]/, 'Passwort muss mindestens einen Kleinbuchstaben enthalten')
    .regex(/[0-9]/, 'Passwort muss mindestens eine Zahl enthalten')
    .regex(/[^A-Za-z0-9]/, 'Passwort muss mindestens ein Sonderzeichen enthalten'),
  geburtsdatum: z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/, 'Ungültiges Datumsformat (YYYY-MM-DD)')
    .optional(),
});

// Validierungs-Middleware
function validiereAnfrage<T extends z.ZodSchema>(schema: T) {
  return (req: Request, res: Response, next: NextFunction) => {
    const ergebnis = schema.safeParse(req.body);

    if (!ergebnis.success) {
      return res.status(400).json({
        fehler: 'Validierungsfehler',
        details: ergebnis.error.issues.map(issue => ({
          feld: issue.path.join('.'),
          nachricht: issue.message,
        })),
      });
    }

    req.body = ergebnis.data; // Sanitisierte Daten verwenden
    next();
  };
}

// Verwendung
app.post(
  '/api/registrierung',
  rateLimitMiddleware,
  validiereAnfrage(RegistrierungsSchema),
  registriereBenutzer
);

SQL-Injection verhindern

// SCHLECHT: SQL-Injection anfällig
const benutzer = await db.query(
  `SELECT * FROM benutzer WHERE name = '${req.params.name}'`
);

// GUT: Parametrisierte Abfragen
const benutzer = await db.query(
  'SELECT * FROM benutzer WHERE name = $1',
  [req.params.name]
);

// GUT: ORM verwenden (Prisma)
const benutzer = await prisma.benutzer.findUnique({
  where: { name: req.params.name },
});

Sicherheitsheader

// Umfassende Sicherheitsheader
function sicherheitsHeader(req: Request, res: Response, next: NextFunction) {
  // Verhindert MIME-Type-Sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Clickjacking-Schutz
  res.setHeader('X-Frame-Options', 'DENY');

  // XSS-Filter (für ältere Browser)
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Referrer-Policy
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Content Security Policy
  res.setHeader('Content-Security-Policy', [
    "default-src 'self'",
    "script-src 'self'",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "connect-src 'self' https://api.meine-app.de",
    "font-src 'self'",
    "frame-ancestors 'none'",
  ].join('; '));

  // Strict Transport Security
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=63072000; includeSubDomains; preload'
  );

  // Permissions Policy
  res.setHeader('Permissions-Policy', [
    'camera=()',
    'microphone=()',
    'geolocation=()',
    'payment=()',
  ].join(', '));

  next();
}

CORS richtig konfigurieren

import cors from 'cors';

// SCHLECHT: Alles erlauben
app.use(cors({ origin: '*' }));

// GUT: Spezifische Konfiguration
const erlaubteUrsprünge = [
  'https://meine-app.de',
  'https://admin.meine-app.de',
];

if (process.env.NODE_ENV === 'development') {
  erlaubteUrsprünge.push('http://localhost:3000');
}

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || erlaubteUrsprünge.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Nicht erlaubt durch CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
  exposedHeaders: ['X-RateLimit-Remaining', 'X-RateLimit-Reset'],
  credentials: true,
  maxAge: 86400,  // Preflight-Cache: 24 Stunden
}));

GraphQL-spezifische Sicherheit

Query-Tiefe und -Komplexität begrenzen

import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    // Maximale Verschachtelungstiefe
    depthLimit(5),

    // Maximale Query-Komplexität
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 5,
      listFactor: 10,
    }),
  ],
  plugins: [
    // Query-Größe begrenzen
    {
      async requestDidStart() {
        return {
          async didResolveOperation(ctx) {
            const queryLaenge = ctx.request.query?.length || 0;
            if (queryLaenge > 10000) {
              throw new Error('Query zu lang');
            }
          },
        };
      },
    },
  ],
});

Introspection in der Produktion deaktivieren

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
});

API-Monitoring und Logging

Sicherheitsrelevante Ereignisse protokollieren

interface SicherheitsLog {
  zeitstempel: string;
  ereignisTyp: 'auth_erfolg' | 'auth_fehler' | 'zugriff_verweigert' |
               'rate_limit' | 'validierung_fehler' | 'verdaechtig';
  ip: string;
  benutzerId?: string;
  endpunkt: string;
  methode: string;
  details: Record<string, unknown>;
}

function protokolliereSicherheitsEreignis(log: SicherheitsLog) {
  // Strukturiertes Logging
  console.log(JSON.stringify({
    ...log,
    zeitstempel: new Date().toISOString(),
    umgebung: process.env.NODE_ENV,
  }));

  // Bei kritischen Ereignissen: Alert auslösen
  if (log.ereignisTyp === 'verdaechtig') {
    benachrichtigeSicherheitsteam(log);
  }
}

// Fehlgeschlagene Anmeldungen überwachen
app.post('/api/auth/login', async (req, res) => {
  const ergebnis = await authentifiziere(req.body);

  if (!ergebnis.erfolg) {
    protokolliereSicherheitsEreignis({
      zeitstempel: new Date().toISOString(),
      ereignisTyp: 'auth_fehler',
      ip: req.ip,
      endpunkt: '/api/auth/login',
      methode: 'POST',
      details: {
        email: req.body.email,
        grund: ergebnis.grund,
        versuch: ergebnis.versuchsNummer,
      },
    });
  }
});

Sicherheits-Checkliste für APIs

Verwenden Sie diese Checkliste als Referenz bei jedem API-Projekt:

Authentifizierung

  • JWT mit asymmetrischer Signierung (RS256/ES256)
  • Kurze Token-Lebensdauer (max. 15 Minuten)
  • Refresh Token Rotation implementiert
  • PKCE für OAuth-Flows aktiviert
  • Multi-Faktor-Authentifizierung verfügbar

Autorisierung

  • Object-Level Authorization für jeden Endpunkt
  • Function-Level Authorization für Admin-Endpunkte
  • Principle of Least Privilege umgesetzt
  • Keine sensiblen Daten in Token-Payloads

Eingabevalidierung

  • Alle Eingaben serverseitig validiert
  • Parametrisierte Datenbankabfragen
  • Request-Größe begrenzt
  • Content-Type-Validierung

Rate Limiting

  • Globales Rate Limit aktiv
  • Schärferes Limit für Auth-Endpunkte
  • Rate-Limit-Header in Antworten

Transport

  • TLS 1.3 erzwungen
  • HSTS aktiviert
  • Sicherheitsheader gesetzt
  • CORS korrekt konfiguriert

Monitoring

  • Sicherheitsereignisse protokolliert
  • Alerting für verdächtige Aktivitäten
  • API-Inventar aktuell

Fazit

API-Sicherheit ist keine einmalige Aufgabe, sondern ein fortlaufender Prozess. Beginnen Sie mit den grundlegenden Maßnahmen -- Authentifizierung, Autorisierung, Eingabevalidierung und Rate Limiting -- und erweitern Sie schrittweise auf fortgeschrittene Techniken wie Anomalie-Erkennung und automatisierte Sicherheitstests.

Nutzen Sie unseren JSON-Formatierer, um API-Anfragen und -Antworten zu analysieren, den Base64-Kodierer zum Dekodieren von JWT-Tokens, und den Hash-Generator zum Verifizieren von Integritätsprüfungen.

Verwandte Ressourcen

Verwandte Beiträge