feat: Add CORS middleware untuk akses dari browser lokal

This commit is contained in:
mwpn
2025-12-17 13:56:32 +07:00
parent bbe4cc000b
commit 89c9ea93c8
3 changed files with 142 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Bootstrap;
use App\Middleware\CorsMiddleware;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Middleware\BodyParsingMiddleware;
@@ -19,6 +20,9 @@ class AppBootstrap
{
$app = AppFactory::create();
// Add CORS middleware FIRST (before routing)
$app->add(new CorsMiddleware());
// Add body parsing middleware
$app->addBodyParsingMiddleware();

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Config\AppConfig;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Factory\ResponseFactory;
class CorsMiddleware implements MiddlewareInterface
{
private array $allowedOrigins;
private array $allowedMethods;
private array $allowedHeaders;
private bool $allowCredentials;
public function __construct()
{
// Load allowed origins from ENV or use defaults
$originsEnv = AppConfig::get('CORS_ALLOWED_ORIGINS', '*');
$this->allowedOrigins = $originsEnv === '*'
? ['*']
: array_map('trim', explode(',', $originsEnv));
// Allowed HTTP methods
$methodsEnv = AppConfig::get('CORS_ALLOWED_METHODS', 'GET,POST,PUT,DELETE,OPTIONS');
$this->allowedMethods = array_map('trim', explode(',', $methodsEnv));
// Allowed headers
$headersEnv = AppConfig::get(
'CORS_ALLOWED_HEADERS',
'Content-Type,Authorization,X-API-KEY,Accept,Origin'
);
$this->allowedHeaders = array_map('trim', explode(',', $headersEnv));
// Allow credentials
$this->allowCredentials = AppConfig::get('CORS_ALLOW_CREDENTIALS', 'true') === 'true';
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$origin = $request->getHeaderLine('Origin');
// Handle preflight OPTIONS request
if ($request->getMethod() === 'OPTIONS') {
$responseFactory = new ResponseFactory();
$response = $responseFactory->createResponse(204); // No Content
return $this->addCorsHeaders($response, $origin);
}
// Process the request
$response = $handler->handle($request);
// Add CORS headers to response
return $this->addCorsHeaders($response, $origin);
}
private function addCorsHeaders(
ResponseInterface $response,
string $origin
): ResponseInterface {
// Determine allowed origin
$allowedOrigin = $this->getAllowedOrigin($origin);
if ($allowedOrigin) {
$response = $response->withHeader('Access-Control-Allow-Origin', $allowedOrigin);
}
if ($this->allowCredentials && $allowedOrigin !== '*') {
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
}
$response = $response->withHeader(
'Access-Control-Allow-Methods',
implode(', ', $this->allowedMethods)
);
$response = $response->withHeader(
'Access-Control-Allow-Headers',
implode(', ', $this->allowedHeaders)
);
$response = $response->withHeader('Access-Control-Max-Age', '86400'); // 24 hours
return $response;
}
private function getAllowedOrigin(string $origin): ?string
{
// If no origin header, return null (not a CORS request)
if (empty($origin)) {
return null;
}
// If wildcard is allowed, return it
if (in_array('*', $this->allowedOrigins, true)) {
return '*';
}
// Check if origin is in allowed list
if (in_array($origin, $this->allowedOrigins, true)) {
return $origin;
}
// Check for localhost variations
$localhostPatterns = [
'http://localhost',
'http://127.0.0.1',
'http://localhost:',
'http://127.0.0.1:',
];
foreach ($localhostPatterns as $pattern) {
if (str_starts_with($origin, $pattern)) {
return $origin;
}
}
// Default: return first allowed origin (fallback)
return $this->allowedOrigins[0] ?? null;
}
}