Files
michaelschiemer/src/Framework/Http/Middlewares/CsrfMiddleware.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

102 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Http\Middlewares;
use App\Framework\DI\Container;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\Method;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\Request;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Session\SessionInterface;
use App\Framework\Security\CsrfToken;
#[MiddlewarePriorityAttribute(MiddlewarePriority::SECURITY)] // Explizite Reihenfolge in MiddlewareManager
final readonly class CsrfMiddleware implements HttpMiddleware
{
public function __construct(
private Container $container,
) {
}
public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
{
$request = $context->request;
// Skip CSRF validation for API routes temporarily for testing
if (str_starts_with($request->path, '/api/')) {
error_log("CsrfMiddleware: Skipping CSRF validation for API route: " . $request->path);
return $next($context);
}
// Try to get session from container - graceful fallback if not available
try {
$session = $this->container->get(SessionInterface::class);
} catch (\Throwable $e) {
// Session not available - skip CSRF validation gracefully
error_log("CsrfMiddleware: Session not available, skipping CSRF validation");
return $next($context);
}
if (in_array($request->method, [Method::POST, Method::PUT, Method::DELETE, Method::PATCH])) {
$this->validateCsrfToken($request, $session);
}
$context = $next($context);
// After successful request processing, optionally rotate token for enhanced security
// This can be enabled for high-security applications
// $this->rotateTokenIfNeeded($request, $session);
return $context;
}
/**
* Validates CSRF token from the request
*/
private function validateCsrfToken(Request $request, SessionInterface $session): void
{
$formId = $request->parsedBody->get('_form_id') ??
$request->headers->getFirst('X-CSRF-Form-ID');
$tokenValue = $request->parsedBody->get('_token') ??
$request->headers->getFirst('X-CSRF-Token');
// Debug: Log CSRF validation attempt
error_log("CSRF Debug: Validating tokens for form_id='$formId'");
if (! $formId || ! $tokenValue) {
throw new \InvalidArgumentException('CSRF protection requires both form ID and token');
}
try {
$token = CsrfToken::fromString($tokenValue);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException('Invalid CSRF token format: ' . $e->getMessage());
}
if (! $session->csrf->validateToken($formId, $token)) {
error_log("CSRF validation failed for form: " . $formId);
throw new \RuntimeException('CSRF token validation failed. This may indicate a security threat.');
}
}
/**
* Rotates CSRF token after successful form submission for enhanced security
*/
private function rotateTokenIfNeeded($request): void
{
$formId = $request->parsedBody->get('_form_id');
if ($formId) {
// Generate a fresh token for the next request
$this->session->csrf->rotateToken($formId);
}
}
}