jarir-ahmed / universal-cors
A secure-by-default, framework-agnostic CORS handler for PHP (plain PHP, Yii2, Laravel, Slim).
Package info
github.com/jarir2020/jarir-ahmed-universal-cors
pkg:composer/jarir-ahmed/universal-cors
Requires
- php: >=7.4
Requires (Dev)
- phpunit/phpunit: ^9.0
README
A secure-by-default, framework-agnostic CORS handler for PHP. One small class that
answers preflight requests and emits the right Access-Control-* headers — without the
classic footguns (* + credentials, blanket origin reflection, forced debug mode).
Why
CORS is usually "fixed" by pasting header("Access-Control-Allow-Origin: *") plus
Access-Control-Allow-Credentials: true into a front controller. That combination is
forbidden by the spec and lets any website call your API with the user's cookies.
This package makes the safe path the easy path.
Requirements
- PHP >= 7.4
Install
composer require jarir-ahmed/universal-cors
Usage (plain PHP / any framework)
Put this at the very top of your front controller or API entry point:
use JarirAhmed\UniversalCors\Cors; $cors = new Cors([ 'allowOrigins' => ['https://app.example.com', 'http://localhost:3000'], 'allowMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], 'allowHeaders' => ['Content-Type', 'Authorization'], 'allowCredentials' => true, 'maxAge' => 3600, ]); // Emits headers; returns true if this was a preflight (then stop). if ($cors->send()) { exit; } // ... your normal request handling continues here ...
Configuration
| Key | Default | Notes |
|---|---|---|
allowOrigins |
[] |
Exact origins, or ['*'] (only without credentials). |
allowOriginPatterns |
[] |
Wildcard patterns, e.g. https://*.example.com. * matches one DNS label and never crosses a dot. |
allowMethods |
GET, POST, PUT, PATCH, DELETE, OPTIONS |
|
allowHeaders |
Content-Type, Authorization |
Use ['*'] to reflect the browser's requested headers. |
exposeHeaders |
[] |
Response headers JS may read. |
allowCredentials |
false |
When true, the matching origin is reflected (never *). |
maxAge |
0 |
Preflight cache seconds. |
Safety guarantees
allowOrigins: ['*']withallowCredentials: truethrows on construction.- With credentials, the request
Originis reflected for allowed origins — never a blanket*— andVary: Originis set for correct caching. - Unlisted origins receive no
Access-Control-Allow-Originheader, so the browser blocks the response.
Testing without sending headers
resolve() and handle() are pure and return a CorsResult you can assert on:
$result = $cors->handle($_SERVER); // or resolve($origin, $method, $requestHeaders) $result->headers; // array<string,string> of response headers $result->isPreflight; // bool $result->status; // 204 for a preflight to short-circuit, else null $result->isAllowed(); // whether an Allow-Origin header was produced
Framework notes
- Plain PHP / Slim / custom: call
$cors->send()early;exiton preflight. - Yii2: prefer the built-in
yii\filters\Corsbehavior; use this package when you want one consistent CORS policy across non-Yii entry points too. - Laravel: register
send()in a global middleware'shandle()before the app runs.
License
MIT