/ CORS

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

OptionTypeDescription
originsarrayAllowed origin domains
methodsarrayAllowed HTTP methods
headersarrayAllowed request headers
expose_headersarrayHeaders browser can access
credentialsboolAllow cookies/auth headers
max_ageintPreflight 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

  1. Browser sends OPTIONS request before actual request
  2. Server responds with CORS headers
  3. Browser caches response for max_age seconds
  4. 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 productionAvoid wildcard origins with credentials
Whitelist specific originsUse exact domains instead of patterns
Limit allowed methodsOnly allow methods you actually use
Limit allowed headersOnly allow headers you actually need
Use HTTPSEncrypt 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
]);