Cross-Origin Resource Sharing (CORS) enforces security policies that restrict which origins may access resources on a server. Proper configuration is essential to allow legitimate cross-origin interactions while preventing unauthorized access.
CORS Activation Scenarios
A request triggers CORS processing when it crosses origins and meets certain conditions:
- Same origin: Request porceeds normally.
- Different origin, simple request: Sent directly; server validates response headers.
- Different origin, non-simple request: Browser first sends an
OPTIONSpreflight request. If the server approves, the actual request follows.
A simple request satisfies all of:
- Method is
GET,POST, orHEAD. - Only safe headers are present (
Accept,Accept-Language,Content-Language,Content-Typelimited toapplication/x-www-form-urlencoded,multipart/form-data,text/plain).
Non-simple requests invoke a preflight because browsers skip the preliminary check for simple ones. This means the server still executes the request even if the client cannot read the response due to missing or incorrect CORS headers, potentially causing security issues.
Preflight example initiated by browser:
OPTIONS /resource/data HTTP/1.1
Origin: https://untrusted-site.org
Sec-Fetch-Dest: empty
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Server response permitting the operation:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://untrusted-site.org
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Recommended Configuration Practices
Origin Whitelisting
Implement middleware that inspects the Origin header before request handling. Permit the request only if the origin matches a predefined list. For OPTIONS requests, immediately reply with 204 No Content. Otherwise, continue processing.
Allowed Methods
Specify exact methods instead of using *. Define a whitelsit such as GET, POST, PUT, DELETE.
Permitted Headers
Avoid wildcard for headers. Explicitly enumerate allowed entries. Authorization must always be listed individually. When serving range-based media, include Range in the whitelist.
Credential Handling
Enable credentials by setting Access-Control-Allow-Credentials to true. In this mode, Access-Control-Allow-Origin must specify a concrete origin—not *.
Exposed Headers
Optionally set Access-Control-Expose-Headers to make additional response headers readable by client-side scripts when they fall outside the default exposed set.
Implementation Example (Node.js/Express)
const trustedDomains = ['https://client-app.com', 'https://service.client-app.com'];
app.use((request, response, nextFn) => {
const incomingOrigin = request.headers.origin;
if (trustedDomains.includes(incomingOrigin)) {
response.set('Access-Control-Allow-Origin', incomingOrigin);
response.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
response.set('Access-Control-Allow-Credentials', 'true');
}
if (request.method === 'OPTIONS') {
return response.status(204).end();
}
nextFn();
});
Additional considerations:
- Cache preflight responses via
Access-Control-Max-Ageto reduce repeated checks. - On CORS failure, browsers log detailed errors in developer tools but block JavaScript from accessing the response body, limiting error insight programmatically.