CORS
What is CORS?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls how web pages can request resources from a different origin (domain, protocol, or port) than the one that served the page.
The Same-Origin Policy (SOP) is the underlying rule: by default, a script on https://app.example.com cannot make AJAX requests to https://api.other.com. CORS is the standard way to relax this restriction in a controlled manner.
An "origin" is defined as the combination of: protocol + hostname + port. Any difference makes it cross-origin.
CORS Headers
| Header | Direction | Purpose |
|---|---|---|
| Access-Control-Allow-Origin | Response | Which origins are allowed (* or specific origin) |
| Access-Control-Allow-Methods | Response | Allowed HTTP methods (GET, POST, PUT, DELETE) |
| Access-Control-Allow-Headers | Response | Allowed request headers |
| Access-Control-Allow-Credentials | Response | Whether cookies/auth headers are allowed |
| Access-Control-Max-Age | Response | How long to cache preflight results (seconds) |
| Origin | Request | The origin of the requesting page |
// api.php — Enable CORS headers
// Allow a specific origin (recommended over *)
$allowedOrigins = ['https://app.example.com', 'https://www.example.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin");
} else {
// Or allow all origins (less secure, fine for public APIs)
// header('Access-Control-Allow-Origin: *');
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Allow-Credentials: true'); // required for cookies/auth
header('Access-Control-Max-Age: 86400'); // cache preflight for 24 hours
// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204); // No Content
exit;
}
// Continue with normal request handling...
header('Content-Type: application/json');
echo json_encode(['message' => 'CORS-enabled response']);
Simple vs Preflight Requests
Simple requests do not trigger a preflight. They must use GET, HEAD, or POST with only safe headers (Content-Type: application/x-www-form-urlencoded, multipart/form-data, or text/plain).
Preflight requests are automatically sent by the browser as an HTTP OPTIONS request before the actual request. They occur when the request uses a non-simple method (PUT, DELETE, PATCH), a non-simple Content-Type (like application/json), or custom headers (like Authorization).
// npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// Option 1: Allow all origins (public API)
app.use(cors());
// Option 2: Allow specific origins with full config
app.use(cors({
origin: ['https://app.example.com', 'https://www.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // allow cookies and auth headers
maxAge: 86400 // cache preflight for 24 hours
}));
// Option 3: Dynamic origin validation
app.use(cors({
origin: function (origin, callback) {
const whitelist = ['https://app.example.com'];
if (!origin || whitelist.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
app.get('/api/data', (req, res) => {
res.json({ message: 'CORS-enabled response' });
});
app.listen(3000);
// ---- PROXY APPROACH (recommended modern solution) ----
// Your own server proxies the request to the third-party API
// Client → Your Server → Third-Party API → Your Server → Client
// No CORS issue because the server-to-server call is not browser-restricted
// Client code (calls your own server)
fetch('/proxy/weather?city=London')
.then(res => res.json())
.then(data => console.log(data));
// Your server (proxy.php or Express route) forwards to the real API:
// $response = file_get_contents("https://api.weather.com/v1/city=London&key=SECRET");
// echo $response;
// ---- JSONP (legacy — only GET, avoid in new code) ----
// JSONP works by injecting a <script> tag — not a real AJAX request
// The server wraps the response in a callback function call
function handleWeatherData(data) {
console.log('Temperature:', data.temp);
}
// Dynamically create a script tag
const script = document.createElement('script');
script.src = 'https://api.example.com/weather?city=London&callback=handleWeatherData';
document.head.appendChild(script);
// Server responds with: handleWeatherData({"temp": 22, "city": "London"})
// The browser executes it as JavaScript, calling our function
// NOTE: JSONP is a security risk and only supports GET.
// Use CORS or a proxy instead.
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.