# CSRF-Schutz

> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des CSRF-Schutzes korrekt dar.

## Übersicht

Cross-Site Request Forgery (CSRF) ist eine Art von Angriff, bei dem ein Angreifer einen Benutzer dazu bringt, unbeabsichtigt Aktionen auf einer Website auszuführen, bei der er bereits authentifiziert ist. Das Framework bietet einen robusten CSRF-Schutzmechanismus, der solche Angriffe verhindert, indem es einzigartige Token generiert und validiert.

## Hauptkomponenten

### CsrfToken

Die `CsrfToken`-Klasse ist ein Value Object, das einen CSRF-Token repräsentiert:

```php
use App\Framework\Security\CsrfToken;

// Token aus einem String erstellen
$token = CsrfToken::fromString($tokenString);

// Token-Wert als String abrufen
$tokenString = $token->toString();
// oder
$tokenString = (string)$token;
```

Die Klasse stellt sicher, dass CSRF-Token immer gültige 64-Zeichen lange hexadezimale Strings sind und bietet Typsicherheit für CSRF-Token-Operationen.

### CsrfTokenGenerator

Die `CsrfTokenGenerator`-Klasse ist verantwortlich für die Generierung neuer CSRF-Token:

```php
use App\Framework\Security\CsrfTokenGenerator;

// Generator initialisieren
$generator = new CsrfTokenGenerator($randomGenerator);

// Neues Token generieren
$token = $generator->generate();
```

Die Klasse verwendet einen kryptografisch sicheren Zufallszahlengenerator, um Token zu erzeugen, die nicht vorhersehbar sind.

### CsrfTokenData

Die `CsrfTokenData`-Klasse erweitert das einfache Token um zusätzliche Metadaten:

```php
use App\Framework\Security\CsrfTokenData;

// Token-Daten mit Metadaten erstellen
$tokenData = new CsrfTokenData(
    $token,
    $clientIp,
    $targetPath,
    $expiresAt
);

// Prüfen, ob das Token abgelaufen ist
if ($tokenData->isExpired($clock)) {
    // Token ist abgelaufen
}

// Prüfen, ob das Token bereits verwendet wurde
if ($tokenData->isUsed()) {
    // Token wurde bereits verwendet
}
```

Diese Klasse ermöglicht erweiterte Sicherheitsfunktionen wie Token-Ablauf, Verwendungsverfolgung und Bindung an bestimmte Pfade oder IP-Adressen.

## Verwendung

### Token generieren und in Formularen verwenden

```php
// In einem Controller
public function showForm(): Response
{
    // Token generieren
    $csrfToken = $this->csrfTokenGenerator->generate();
    
    // Token in der Session speichern
    $this->session->set('csrf_token', $csrfToken->toString());
    
    // Token im Formular einbinden
    return $this->render('form.twig', [
        'csrf_token' => $csrfToken->toString(),
    ]);
}
```

In der Formularvorlage:

```html
<form method="post" action="/submit">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <!-- Weitere Formularfelder -->
    <button type="submit">Absenden</button>
</form>
```

### Token validieren

```php
// In einem Controller
public function handleForm(Request $request): Response
{
    // Gespeichertes Token abrufen
    $storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
    
    // Übermitteltes Token abrufen
    $submittedToken = $request->getPostParam('csrf_token');
    
    // Token vergleichen (zeitsichere Vergleichsmethode)
    if (!$storedToken->equalsString($submittedToken)) {
        throw new SecurityException('Ungültiges CSRF-Token');
    }
    
    // Token ist gültig, Formular verarbeiten
    // ...
}
```

### Erweiterte Token-Validierung mit Metadaten

```php
// In einem Controller
public function showForm(): Response
{
    // Token mit Metadaten generieren
    $token = $this->csrfTokenGenerator->generate();
    $tokenData = new CsrfTokenData(
        $token,
        $this->request->getClientIp(),
        $this->request->path,
        new \DateTimeImmutable('+1 hour')
    );
    
    // Token-Daten in der Session speichern
    $this->session->set('csrf_token_data', $tokenData->toArray());
    
    // Token im Formular einbinden
    return $this->render('form.twig', [
        'csrf_token' => $token->toString(),
    ]);
}

public function handleForm(Request $request): Response
{
    // Token-Daten aus der Session abrufen
    $tokenDataArray = $this->session->get('csrf_token_data');
    $tokenData = CsrfTokenData::fromArray($tokenDataArray);
    
    // Übermitteltes Token abrufen
    $submittedToken = $request->getPostParam('csrf_token');
    
    // Umfassende Validierung durchführen
    if ($tokenData->isExpired($this->clock)) {
        throw new SecurityException('CSRF-Token ist abgelaufen');
    }
    
    if ($tokenData->isUsed()) {
        throw new SecurityException('CSRF-Token wurde bereits verwendet');
    }
    
    if ($tokenData->clientIp !== $request->getClientIp()) {
        throw new SecurityException('CSRF-Token ist an eine andere IP-Adresse gebunden');
    }
    
    if (!$tokenData->token->equalsString($submittedToken)) {
        throw new SecurityException('Ungültiges CSRF-Token');
    }
    
    // Token als verwendet markieren
    $tokenData->markAsUsed();
    $this->session->set('csrf_token_data', $tokenData->toArray());
    
    // Token ist gültig, Formular verarbeiten
    // ...
}
```

## Integration mit dem Framework

### CSRF-Middleware

Das Framework bietet eine `CsrfProtectionMiddleware`, die automatisch CSRF-Schutz für alle POST, PUT, DELETE und PATCH-Anfragen implementiert:

```php
// In der Bootstrap-Datei oder Router-Konfiguration
$app->addMiddleware(CsrfProtectionMiddleware::class);
```

Die Middleware:
1. Generiert automatisch CSRF-Token für alle Formulare
2. Validiert Token für alle Anfragen, die den Zustand ändern
3. Wirft eine Exception oder gibt eine Fehlerantwort zurück, wenn die Validierung fehlschlägt

### Konfiguration

Die CSRF-Schutzfunktionen können in der Konfigurationsdatei angepasst werden:

```php
// config/security.php
return [
    'csrf' => [
        'enabled' => true,
        'token_lifetime' => 3600, // Sekunden
        'exclude_routes' => [
            '/api/webhook',
            '/api/external/callback',
        ],
        'exclude_patterns' => [
            '#^/api/public/#', // Regulärer Ausdruck für auszuschließende Routen
        ],
        'header_name' => 'X-CSRF-Token', // Name des HTTP-Headers für API-Anfragen
        'parameter_name' => 'csrf_token', // Name des Formularfelds
        'strict_mode' => true, // Strikte Validierung (IP-Bindung, Einmalverwendung)
    ],
];
```

### Template-Integration

Das Framework bietet eine einfache Möglichkeit, CSRF-Token in Templates einzubinden:

```php
// In einem Controller
public function showForm(): Response
{
    return $this->render('form.twig', [
        'csrf_token' => $this->csrfProtection->getToken()->toString(),
    ]);
}
```

Mit der Template-Funktion:

```twig
{# In einer Twig-Vorlage #}
<form method="post" action="/submit">
    {{ csrf_field() }}
    <!-- Weitere Formularfelder -->
    <button type="submit">Absenden</button>
</form>
```

Die `csrf_field()`-Funktion generiert automatisch ein verstecktes Formularfeld mit dem aktuellen CSRF-Token.

### API-Integration

Für API-Anfragen kann das CSRF-Token über einen HTTP-Header übermittelt werden:

```javascript
// JavaScript-Beispiel
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify(data)
});
```

In der HTML-Vorlage:

```html
<meta name="csrf-token" content="{{ csrf_token }}">
```

## Erweiterte Funktionen

### Double Submit Cookie Pattern

Das Framework unterstützt das Double Submit Cookie Pattern für zusätzliche Sicherheit:

```php
// In einem Controller
public function showForm(): Response
{
    $token = $this->csrfTokenGenerator->generate();
    
    // Token in der Session speichern
    $this->session->set('csrf_token', $token->toString());
    
    // Token auch als Cookie setzen
    $this->response->setCookie('csrf_token', $token->toString(), [
        'httponly' => false, // JavaScript muss darauf zugreifen können
        'samesite' => 'Strict',
        'secure' => true,
        'path' => '/',
    ]);
    
    return $this->render('form.twig', [
        'csrf_token' => $token->toString(),
    ]);
}
```

### Token-Rotation

Für zusätzliche Sicherheit können CSRF-Token nach jeder Anfrage rotiert werden:

```php
// In einem Controller
public function handleForm(Request $request): Response
{
    // Token validieren
    // ...
    
    // Neues Token generieren
    $newToken = $this->csrfTokenGenerator->generate();
    $this->session->set('csrf_token', $newToken->toString());
    
    // Antwort mit neuem Token
    return $this->render('success.twig', [
        'new_csrf_token' => $newToken->toString(),
    ]);
}
```

### Synchronizer Token Pattern

Das Framework implementiert das Synchronizer Token Pattern, bei dem für jede Sitzung ein eindeutiges Token generiert wird:

```php
// In der Session-Initialisierung
public function initializeSession(): void
{
    if (!$this->session->has('csrf_token')) {
        $token = $this->csrfTokenGenerator->generate();
        $this->session->set('csrf_token', $token->toString());
    }
}
```

## Fehlerbehebung

### Häufige Probleme

#### Token-Validierung schlägt fehl

Mögliche Ursachen:
- Das Token ist abgelaufen
- Das Token wurde bereits verwendet
- Das Token wurde nicht korrekt übermittelt
- Die Session ist abgelaufen oder wurde zurückgesetzt

Lösung:
- Überprüfen Sie die Session-Konfiguration
- Stellen Sie sicher, dass das Token korrekt im Formular eingebunden ist
- Erhöhen Sie die Token-Lebensdauer in der Konfiguration

#### CSRF-Schutz für bestimmte Routen deaktivieren

Wenn Sie den CSRF-Schutz für bestimmte Routen deaktivieren müssen (z.B. für Webhook-Callbacks):

```php
// config/security.php
return [
    'csrf' => [
        'exclude_routes' => [
            '/api/webhook',
            '/api/external/callback',
        ],
    ],
];
```

#### AJAX-Anfragen mit CSRF-Schutz

Für AJAX-Anfragen müssen Sie das CSRF-Token im Header oder als Formularfeld übermitteln:

```javascript
// JavaScript mit jQuery
$.ajaxSetup({
    headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
    }
});

// Oder mit Fetch API
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify(data)
});
```

## Sicherheitsüberlegungen

### Best Practices

1. **Verwenden Sie HTTPS**: CSRF-Token sollten immer über eine verschlüsselte Verbindung übertragen werden.
2. **Kurze Token-Lebensdauer**: Setzen Sie die Token-Lebensdauer auf einen angemessenen Wert (z.B. 1-2 Stunden).
3. **Strikte SameSite-Cookie-Einstellungen**: Verwenden Sie `SameSite=Strict` oder `SameSite=Lax` für Session-Cookies.
4. **Token-Rotation**: Rotieren Sie Token nach jeder Anfrage, die den Zustand ändert.
5. **Validieren Sie alle Anfragen**: Stellen Sie sicher, dass alle Anfragen, die den Zustand ändern, ein gültiges CSRF-Token erfordern.

### Bekannte Einschränkungen

- CSRF-Schutz funktioniert nicht für Benutzer, die keine Cookies akzeptieren.
- Bei sehr langen Sitzungen kann die Sicherheit des CSRF-Tokens beeinträchtigt sein.
- CSRF-Schutz kann mit bestimmten Caching-Strategien in Konflikt geraten.

## Weiterführende Informationen

- [Security Features Übersicht](index.md)
- [Security Headers und CSP](security-headers.md)
- [Request Signing API](request-signing.md)
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
