The Access-Control-Allow-Origin
(ACAO) header is a critical part of the Cross-Origin Resource Sharing (CORS) mechanism. It allows servers to specify which origins (domains) are permitted to access their resources via web browsers. Here’s a breakdown of how it works:
1. Purpose
- Security: Enforces the browser’s same-origin policy, preventing unauthorized cross-origin requests.
- Control: Lets servers explicitly whitelist trusted domains for cross-origin access (e.g., APIs accessed by a frontend app).
2. How It Works
Step 1: Browser Sends a Cross-Origin Request
When a web app at https://example.com
tries to fetch data from https://api.otherdomain.com
, the browser adds an Origin
header to the request:
Origin: https://example.com
Step 2: Server Responds with Access-Control-Allow-Origin
The server at api.otherdomain.com
responds with:
Access-Control-Allow-Origin: https://example.com
- Allowed: If the value matches the
Origin
header, the browser permits the response to be read. - Denied: If there’s a mismatch, the browser blocks the response and throws a CORS error.
3. Common Configurations
Allow a Specific Domain
Access-Control-Allow-Origin: https://example.com
Allow Any Domain (Use Sparingly)
Access-Control-Allow-Origin: *
- Limitation: Wildcard (
*
) cannot be used with credentials (e.g., cookies, auth headers). For authenticated requests, specify exact domains.
4. Handling Credentials (Cookies, Auth Headers)
If the client sends credentials (e.g., fetch(..., { credentials: 'include' })
):
- The server must set:
Access-Control-Allow-Origin: https://example.com // Exact domain, NOT wildcard (*)
Access-Control-Allow-Credentials: true
- The client must explicitly include credentials.
5. Preflight Requests
For “complex” requests (e.g., POST
with Content-Type: application/json
), the browser first sends an OPTIONS request (preflight) to check permissions. The server must respond with:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
6. Example Flow
- Client Request (from
https://example.com
):
GET /data HTTP/1.1
Origin: https://example.com
- Server Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
- Result: The browser allows the response data to be read by
https://example.com
.
7. Common Errors
- Missing ACAO Header: Browser blocks the response.
- Wildcard with Credentials:
Access-Control-Allow-Origin: *
+Access-Control-Allow-Credentials: true
→ error. - Mismatched Origin:
Access-Control-Allow-Origin: https://wrongdomain.com
→ request blocked.
8. Server-Side Setup Examples
Express.js
const cors = require('cors');
app.use(cors({
origin: 'https://example.com', // or an array of allowed origins
credentials: true
}));
Nginx
location / {
add_header 'Access-Control-Allow-Origin' 'https://example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
}
Apache (.htaccess)
Header set Access-Control-Allow-Origin "https://example.com"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Key Takeaways
- Security First: Restrict ACAO to specific domains instead of using
*
. - Credentials: Use explicit domains and
Access-Control-Allow-Credentials: true
. - Preflight: Handle OPTIONS requests for non-simple requests (e.g., custom headers).
The Access-Control-Allow-Origin
header is enforced by browsers, not servers. Tools like curl
or Postman ignore CORS, but browsers strictly enforce it to protect users.