CORS
Miko's CORS class handles Cross-Origin Resource Sharing headers. Enable secure cross-origin requests for your APIs.
CORS Methods Summary
| Method | Description |
|---|---|
handle() | Handle CORS with configuration |
allowAll() | Allow all origins (development) |
allowOrigins() | Allow specific origins |
api() | Common API preset |
Basic Usage
Allow All Origins (Development)
use Miko\Core\Http\Cors;
// Allow all origins - use only in development!
Cors::allowAll();
Specific Origins (Production)
// Allow specific origins
Cors::handle([
'origins' => ['https://example.com', 'https://app.example.com'],
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => ['Content-Type', 'Authorization'],
'credentials' => true,
'max_age' => 86400
]);
Configuration Options
Cors::handle([
// Allowed origins (required)
'origins' => ['https://example.com'],
// Allowed HTTP methods
'methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
// Allowed request headers
'headers' => [
'Content-Type',
'Authorization',
'X-Requested-With',
'Accept',
'Origin'
],
// Headers exposed to browser
'expose_headers' => [
'X-Custom-Header',
'X-Request-Id'
],
// Allow credentials (cookies, authorization headers)
'credentials' => true,
// Preflight cache duration (seconds)
'max_age' => 86400 // 24 hours
]);
Configuration Reference
| Option | Type | Description |
|---|---|---|
origins | array | Allowed origin domains |
methods | array | Allowed HTTP methods |
headers | array | Allowed request headers |
expose_headers | array | Headers browser can access |
credentials | bool | Allow cookies/auth headers |
max_age | int | Preflight cache (seconds) |
Presets
API Preset
Common configuration for REST APIs.
// Quick setup for APIs
Cors::api();
// Equivalent to:
Cors::handle([
'origins' => ['*'],
'methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
'headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],
'credentials' => false,
'max_age' => 86400
]);
Allow Specific Origins
// Only allow specific domains
Cors::allowOrigins([
'https://myapp.com',
'https://admin.myapp.com',
'https://mobile.myapp.com'
]);
Preflight Handling
CORS automatically handles OPTIONS preflight requests.
// At the beginning of your API entry point
Cors::handle([
'origins' => ['https://example.com'],
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => ['Content-Type', 'Authorization']
]);
// OPTIONS requests are automatically handled and exit
// Your API code continues for other methods...
How Preflight Works
- Browser sends OPTIONS request before actual request
- Server responds with CORS headers
- Browser caches response for
max_ageseconds - Browser sends actual request if allowed
Browser Server
| |
|--- OPTIONS /api/users -------->|
| |
|<-- 200 OK + CORS headers ------|
| |
|--- POST /api/users ----------->|
| |
|<-- 201 Created + Data ---------|
Practical Examples
API Entry Point
<?php
// api/index.php
require_once '../Model/Miko/autoload.php';
use Miko\Core\Http\Cors;
use Miko\Core\Http\JsonResponse;
// Handle CORS first
Cors::handle([
'origins' => [
'https://myapp.com',
'https://admin.myapp.com'
],
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => ['Content-Type', 'Authorization'],
'credentials' => true
]);
// Your API logic here
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Route handling...
Environment-Based Configuration
// Different CORS settings per environment
if ($_ENV['APP_ENV'] === 'development') {
// Allow all in development
Cors::allowAll();
} else {
// Strict in production
Cors::handle([
'origins' => explode(',', $_ENV['CORS_ORIGINS']),
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => ['Content-Type', 'Authorization'],
'credentials' => true,
'max_age' => 86400
]);
}
Multiple Subdomains
// Allow all subdomains of example.com
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowedPattern = '/^https:\/\/([a-z0-9-]+\.)?example\.com$/';
if (preg_match($allowedPattern, $origin)) {
Cors::handle([
'origins' => [$origin],
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => ['Content-Type', 'Authorization'],
'credentials' => true
]);
} else {
http_response_code(403);
exit('Origin not allowed');
}
With Authentication
// CORS with JWT authentication
Cors::handle([
'origins' => ['https://myapp.com'],
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'headers' => [
'Content-Type',
'Authorization', // For Bearer token
'X-Requested-With'
],
'expose_headers' => [
'X-Token-Expired' // Custom header for token status
],
'credentials' => true
]);
// Now handle authentication
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// Validate JWT...
Common Issues
Credentials with Wildcard Origin
// This will NOT work:
Cors::handle([
'origins' => ['*'],
'credentials' => true // Error! Can't use * with credentials
]);
// This works:
Cors::handle([
'origins' => ['https://specific-origin.com'],
'credentials' => true
]);
Missing Headers
// If you get "Request header not allowed" errors,
// add the header to the allowed list:
Cors::handle([
'origins' => ['https://example.com'],
'headers' => [
'Content-Type',
'Authorization',
'X-Custom-Header', // Add your custom headers
'X-API-Key'
]
]);
Preflight Not Cached
// Increase max_age to reduce preflight requests
Cors::handle([
'origins' => ['https://example.com'],
'max_age' => 86400 // Cache for 24 hours
]);
Security Best Practices
| Practice | Description |
|---|---|
| Never use * in production | Avoid wildcard origins with credentials |
| Whitelist specific origins | Use exact domains instead of patterns |
| Limit allowed methods | Only allow methods you actually use |
| Limit allowed headers | Only allow headers you actually need |
| Use HTTPS | Encrypt all traffic for all origins |
// Production configuration
Cors::handle([
// Specific origins only
'origins' => [
'https://myapp.com',
'https://admin.myapp.com'
],
// Only methods you use
'methods' => ['GET', 'POST', 'PUT', 'DELETE'],
// Only headers you need
'headers' => ['Content-Type', 'Authorization'],
// Enable if using cookies/auth
'credentials' => true,
// Cache preflight
'max_age' => 86400
]);