CORS on PHP

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

If you don't have access to configure Apache/Nginx, you can still send CORS headers from a PHP script. This is useful for shared hosting or when you need different CORS policies for different endpoints.

Important: As with all uses of the PHP header function, CORS headers must be sent before any output has been sent from the server. Call header() before any echo, print, or HTML output.

Tip: For production applications, consider using a PHP framework like Laravel, Symfony, or Slim which have built-in CORS middleware packages that handle all edge cases automatically.

Method 1: Simple Single Origin (Recommended)

<?php
// Allow from specific origin
header("Access-Control-Allow-Origin: https://example.com");
header("Vary: Origin");

// Your PHP code here

Method 2: Multiple Origins with Validation

To allow multiple specific origins (issue #146):

<?php
$allowedOrigins = [
    'https://example.com',
    'https://app.example.com'
];

$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';

if (in_array($origin, $allowedOrigins)) {
    header("Access-Control-Allow-Origin: $origin");
    header("Vary: Origin");
}

// Your PHP code here

Method 3: With Preflight OPTIONS Handling

For APIs that use custom headers or methods like PUT/DELETE:

<?php
$allowedOrigins = [
    'https://example.com',
    'https://app.example.com'
];

$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';

// Validate origin
if (in_array($origin, $allowedOrigins)) {
    header("Access-Control-Allow-Origin: $origin");
    header("Vary: Origin");
}

// Set other CORS headers
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");

// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header("Access-Control-Max-Age: 86400");
    http_response_code(204);
    exit(0);
}

// Your API logic here

Method 4: With Credentials (Cookies/Sessions)

When using cookies or sessions, you must specify exact origins (wildcards are not allowed):

<?php
$allowedOrigins = [
    'https://example.com',
    'https://app.example.com'
];

$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';

// Validate origin
if (in_array($origin, $allowedOrigins)) {
    header("Access-Control-Allow-Origin: $origin");
    header("Access-Control-Allow-Credentials: true");
    header("Vary: Origin");
}

// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
    header("Access-Control-Allow-Headers: Content-Type, Authorization");
    header("Access-Control-Max-Age: 86400");
    http_response_code(204);
    exit(0);
}

// Your API logic here

Method 5: Reusable CORS Function

Create a reusable function to simplify CORS configuration:

<?php
function handleCors($allowedOrigins = []) {
    $origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';

    // Validate origin
    if (in_array($origin, $allowedOrigins)) {
        header("Access-Control-Allow-Origin: $origin");
        header("Access-Control-Allow-Credentials: true");
        header("Vary: Origin");
    }

    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
    header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");

    // Handle preflight
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        header("Access-Control-Max-Age: 86400");
        http_response_code(204);
        exit(0);
    }
}

// Usage in your API
handleCors(['https://example.com', 'https://app.example.com']);

// Your API logic here
$data = ['message' => 'CORS enabled'];
header('Content-Type: application/json');
echo json_encode($data);

Method 6: Public API (Wildcard - Use with Caution)

Only use for completely public resources:

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");

// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header("Access-Control-Max-Age: 86400");
    http_response_code(204);
    exit(0);
}

// Your public API logic here

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain