CORS Setup
Configure Cross-Origin Resource Sharing (CORS) to allow your API to be accessed from different domains.
Basic CORS Setup
1. Enable CORS in Environment
Add CORS configuration to your .env
file:
# Enable CORS
CORS_ENABLED=true
# Allow specific origins
CORS_ALLOWED_ORIGINS=https://baseapp.com,https://baseapp.dev
# Configure allowed methods and headers
CORS_ALLOWED_METHODS=GET, POST, PUT, DELETE, OPTIONS
CORS_ALLOWED_HEADERS=Content-Type, Authorization, X-Requested-With
# Enable credentials if needed
CORS_ALLOW_CREDENTIALS=true
# Cache preflight for 24 hours
CORS_MAX_AGE=86400
2. Router Handles CORS Automatically
The Router automatically handles CORS when enabled. No additional code needed:
<?php
require 'vendor/autoload.php';
use Stilmark\Base\Env;
use Stilmark\Base\Router;
// Load environment (includes CORS config)
Env::load(__DIR__ . '/.env');
// Router handles CORS preflight automatically
$router = new Router();
$router->dispatch();
CORS Configuration Examples
Development Setup
Allow localhost and development domains:
CORS_ENABLED=true
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080,https://dev.example.com
CORS_ALLOWED_METHODS=GET, POST, PUT, DELETE, OPTIONS
CORS_ALLOWED_HEADERS=Content-Type, Authorization, X-Requested-With, X-Debug-Token
CORS_ALLOW_CREDENTIALS=true
CORS_MAX_AGE=3600
Production Setup
Restrict to specific production domains:
CORS_ENABLED=true
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
CORS_ALLOWED_METHODS=GET, POST, PUT, DELETE, OPTIONS
CORS_ALLOWED_HEADERS=Content-Type, Authorization
CORS_ALLOW_CREDENTIALS=true
CORS_MAX_AGE=86400
Wildcard Subdomain Setup
Allow all subdomains of a domain:
CORS_ENABLED=true
CORS_ALLOWED_ORIGINS=https://*.example.com
CORS_ALLOWED_METHODS=GET, POST, OPTIONS
CORS_ALLOWED_HEADERS=Content-Type, Authorization
CORS_ALLOW_CREDENTIALS=false
CORS_MAX_AGE=86400
Frontend Integration
JavaScript Fetch API
// Simple request (no preflight needed)
fetch('https://api.example.com/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
// Complex request (triggers preflight)
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
credentials: 'include', // Send cookies
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
Axios Configuration
// Configure axios defaults
axios.defaults.withCredentials = true;
axios.defaults.headers.common['Content-Type'] = 'application/json';
// Make requests
axios.post('https://api.example.com/users', {
name: 'John Doe',
email: 'john@example.com'
}, {
headers: {
'Authorization': 'Bearer token123'
}
})
.then(response => console.log(response.data));
CORS Headers Explained
Response Headers Set by Base
When CORS is enabled, Base automatically sets these headers:
Access-Control-Allow-Origin: https://baseapp.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Preflight Request Flow
Browser sends OPTIONS request:
OPTIONS /api/users HTTP/1.1
Origin: https://baseapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
Base responds with CORS headers:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://baseapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Max-Age: 86400
Browser makes actual request:
POST /api/users HTTP/1.1
Origin: https://baseapp.com
Content-Type: application/json
Authorization: Bearer token123
Security Considerations
Origin Validation
Always specify exact origins in production:
# Good: Specific origins
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
# Avoid: Wildcard in production
CORS_ALLOWED_ORIGINS=*
Credentials Handling
Only enable credentials when necessary:
# Enable only if you need cookies/auth headers
CORS_ALLOW_CREDENTIALS=true
# Disable for public APIs
CORS_ALLOW_CREDENTIALS=false
Header Restrictions
Limit allowed headers to what you actually need:
# Minimal headers for simple APIs
CORS_ALLOWED_HEADERS=Content-Type
# Extended headers for complex apps
CORS_ALLOWED_HEADERS=Content-Type, Authorization, X-Requested-With, X-API-Key
Troubleshooting CORS
Common Issues
CORS not working:
Check
CORS_ENABLED=true
in.env
Verify origin is in
CORS_ALLOWED_ORIGINS
Ensure no trailing slashes in origins
Preflight failing:
Check
OPTIONS
is inCORS_ALLOWED_METHODS
Verify all request headers are in
CORS_ALLOWED_HEADERS
Credentials not working:
Set
CORS_ALLOW_CREDENTIALS=true
Cannot use wildcard origin with credentials
Frontend must set
credentials: 'include'
Debug CORS Issues
Add debug logging to see CORS processing:
// In development environment
if (Env::get('APP_ENV') === 'development') {
error_log('CORS Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? 'none'));
error_log('CORS Enabled: ' . Env::get('CORS_ENABLED', 'false'));
error_log('Allowed Origins: ' . Env::get('CORS_ALLOWED_ORIGINS', ''));
}
Browser Developer Tools
Check the Network tab for:
Preflight OPTIONS request
CORS headers in response
Console errors about CORS policy
Testing CORS
Manual Testing with cURL
Test preflight request:
curl -X OPTIONS \
-H "Origin: https://baseapp.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type, Authorization" \
-v \
https://api.example.com/users
Test actual request:
curl -X POST \
-H "Origin: https://baseapp.com" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-d '{"name":"John","email":"john@example.com"}' \
-v \
https://api.example.com/users
Automated Testing
public function testCorsHeaders()
{
Env::set('CORS_ENABLED', 'true');
Env::set('CORS_ALLOWED_ORIGINS', 'https://baseapp.com');
$_SERVER['HTTP_ORIGIN'] = 'https://baseapp.com';
$_SERVER['REQUEST_METHOD'] = 'OPTIONS';
ob_start();
Router::dispatch();
$output = ob_get_clean();
$this->assertEquals(204, http_response_code());
$this->assertStringContainsString('Access-Control-Allow-Origin: https://baseapp.com', $output);
}
Last updated