44 lines
1.4 KiB
PHP
44 lines
1.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Middleware;
|
|
|
|
use App\Core\{Request, Response};
|
|
|
|
final class CsrfMiddleware
|
|
{
|
|
public function handle(Request $request, callable $next): mixed
|
|
{
|
|
// Skip CSRF check for safe methods
|
|
if (in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {
|
|
return $next($request);
|
|
}
|
|
|
|
// For APIs, we often use a custom header or check origin
|
|
// If we use sessions for tokens:
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
$token = $request->getHeader('X-CSRF-TOKEN') ?: ($request->getBody()['_csrf'] ?? null);
|
|
$sessionToken = $_SESSION['csrf_token'] ?? null;
|
|
|
|
if (!$token || !$sessionToken || !hash_equals($sessionToken, $token)) {
|
|
// For now, if we are purely API with Bearer token, we might skip this.
|
|
// But if the request has a session or cookie, it's mandatory.
|
|
|
|
// If the Authorization header is present, we might assume it's an API call
|
|
// that is naturally protected against CSRF if not using cookies for Auth.
|
|
if ($request->getHeader('Authorization')) {
|
|
return $next($request);
|
|
}
|
|
|
|
Response::error('رمز الحماية (CSRF) غير صالح أو مفقود', 'CSRF_INVALID', 403);
|
|
return null;
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|