Cross-origin resource sharing
Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to enable web browsers to make controlled cross-origin requests, allowing servers to specify which external origins can access their resources while relaxing the same-origin policy for legitimate use cases such as APIs and embedded content.[1] Developed as a standard to support secure web mashups and third-party integrations, CORS addresses the limitations of the same-origin policy by providing a standardized way for servers to grant or deny access based on origin, method, and headers.[2] The specification was first published as a W3C Working Draft in May 2006, renamed in 2009, and became a W3C Recommendation in 2014, with its implementation now integrated into the Fetch Living Standard maintained by the WHATWG.[3]
CORS operates through a combination of request and response headers exchanged between the client (browser) and server. When a web page attempts to load a resource from a different origin—defined as a unique combination of scheme, host, and port—the browser includes an Origin header in the request to indicate the requesting site's origin.[4] The server then responds with headers like Access-Control-Allow-Origin to explicitly permit the origin, or omits them to enforce the same-origin restriction, preventing unauthorized access. This process ensures that cross-origin reads are only allowed if the server opts in, mitigating risks such as data leakage in cross-site scripting attacks.[2]
Requests under CORS are classified as simple or preflight based on their characteristics. Simple requests, such as standard GET or POST operations with safe-listed headers (e.g., Accept, Content-Type limited to certain values), are sent directly without prior negotiation, but still require server approval via CORS headers in the response.[5] Preflight requests, triggered for potentially unsafe operations like non-standard methods (e.g., PUT, DELETE) or custom headers, involve an initial OPTIONS request where the browser sends Access-Control-Request-Method and Access-Control-Request-Headers to query permissions.[6] The server responds with Access-Control-Allow-Methods and Access-Control-Allow-Headers to approve, and the actual request proceeds only if authorized; otherwise, the browser blocks the response.[2]
Key security features of CORS include support for credentials (e.g., cookies via Access-Control-Allow-Credentials) only when explicitly allowed, and the Access-Control-Expose-Headers header to limit which response headers the client can read.[7] Widely supported in all major browsers since 2015, CORS is essential for modern web development, powering services like single sign-on and content delivery networks while upholding browser-enforced security boundaries.[2]
Fundamentals
Same-Origin Policy
The same-origin policy (SOP) is a fundamental security mechanism implemented in web browsers that restricts documents or scripts loaded from one origin from interacting with resources from a different origin.[8] An origin is defined as the tuple of scheme (protocol, such as HTTP or HTTPS), host (domain name), and port number; for instance, http://example.com:80 and https://example.com:80 are considered different origins due to the protocol mismatch.[8][9]
Introduced in Netscape Navigator 2.0 in 1995 alongside JavaScript 1.0, the SOP was developed to isolate potentially malicious scripts and prevent them from accessing sensitive data, such as document structures or user session information, across different domains.[9][10] This policy addressed early web security challenges as client-side scripting emerged, ensuring that content from untrusted sources could not interfere with or exfiltrate data from other sites.[9]
Under the SOP, several common actions are blocked when originating from a different domain. For example, JavaScript cannot make cross-origin requests using the XMLHttpRequest object or the Fetch API to read response data from another origin.[8] Similarly, embedding content in an <iframe> from a different origin prevents the parent page's scripts from accessing the iframe's Window or Document objects, such as reading its DOM elements.[8][11] Script execution is also restricted; a script cannot manipulate or inspect the properties of a cross-origin frame, like its location or form data, to avoid unauthorized data access.[12]
Certain resource types are exempt from full SOP restrictions to support web functionality, though with limitations on access. Images loaded via the <img> tag, scripts via the <script> tag, and stylesheets via the <link> or @import directives can be fetched cross-origin and embedded, but JavaScript cannot read their raw content or pixel data (for images).[8][11] For instance, a cross-origin script executes in the context of the loading page but cannot access the DOM or other resources of its original origin.[9] These exceptions enable practical web features like third-party ads or libraries while maintaining security boundaries.[12]
The SOP provides a strict isolation model, which mechanisms like Cross-Origin Resource Sharing (CORS) can selectively relax for authorized interactions.[8]
Purpose of CORS
The same-origin policy (SOP), a foundational web security mechanism, restricts web pages from making requests to a different domain, scheme, or port than the one serving the page, primarily to prevent malicious scripts from accessing sensitive data across origins.[8] This policy effectively blocks legitimate use cases, such as web applications hosted on one domain making API calls to backend services on another domain, which is a standard practice in distributed architectures to separate concerns and improve scalability.[12] Similarly, SOP limits loading static resources like stylesheets, images, or fonts from content delivery networks (CDNs) on external domains, complicating efficient content distribution and requiring workarounds that can introduce security risks or performance overhead.[8]
To mitigate these restrictions while preserving core security, Cross-Origin Resource Sharing (CORS) provides a standardized protocol defined in the W3C recommendation.[3] CORS enables servers to explicitly indicate, through HTTP response headers, which external origins are authorized to access their resources, allowing controlled relaxation of SOP without exposing data indiscriminately.[2] By requiring server-side opt-in, CORS ensures that cross-origin access remains secure, as browsers enforce the policy only when the server consents via specific headers like Access-Control-Allow-Origin.[13]
The primary benefits of CORS lie in facilitating modern web development practices, such as secure asynchronous JavaScript and XML (AJAX) requests for dynamic content loading, embedding web fonts from third-party providers to enhance typography without tainting the page's security context, and enabling cross-origin access to canvas elements for image manipulation in applications like photo editors.[2] These capabilities support richer user experiences, such as real-time data fetching from APIs or integrating multimedia from diverse sources, all while upholding SOP's protections against unauthorized data exfiltration.[3]
CORS specifically governs cross-origin interactions in APIs like XMLHttpRequest and the Fetch API, which are used for programmatic HTTP requests, as well as features involving Web Fonts, WebGL textures, and canvas drawImage() operations.[2] In contrast, CORS does not restrict embedding via <script> or <img> tags, which permit cross-origin loading but impose looser rules—scripts execute freely if allowed by the server, while images can be displayed but their pixel data is often blocked from JavaScript access to prevent data leaks.[2]
Mechanism
Simple Requests
Simple cross-origin requests in Cross-Origin Resource Sharing (CORS) are HTTP requests that meet specific criteria allowing them to be issued directly without a preliminary preflight check, facilitating straightforward access to resources from different origins.[14] These requests are defined as those using CORS-safelisted methods and headers, which align with common, low-risk operations in web applications.[15] The mechanism ensures that browsers can enforce the same-origin policy while permitting benign cross-origin interactions without additional overhead.[16]
A request qualifies as simple if it adheres to strict conditions on methods and headers to avoid potential security risks that might necessitate preflight validation. The HTTP method must be one of GET, HEAD, or POST, as these are considered safe or idempotent operations that do not typically alter server state in unintended ways.[14] Additionally, the request must not include any forbidden methods such as CONNECT, TRACE, or TRACK, nor headers that start with "Sec-" or other non-standard fields that could introduce vulnerabilities.[17] For headers, only a limited set is permitted: accept, accept-language, content-language, content-type (restricted to application/x-www-form-urlencoded, multipart/form-data, or text/plain), dpr (a number between 1.0 and 256.0), downlink, save-data, viewport-width (a number between 1 and 10000), width (a number between 1 and 10000), and range (a single "bytes" range); all header values must have length not exceeding 128 bytes and contain no CORS-unsafe bytes.[18] No custom or author-defined headers are allowed, ensuring the request remains innocuous and compatible with legacy user agents.[18]
The flow for a simple request begins when a web application attempts to access a cross-origin resource, prompting the user agent (browser) to include an Origin header in the request, specifying the requesting site's origin.[19] The browser then sends this request directly to the target server without any prior OPTIONS preflight, as the simplicity criteria are met. Upon receiving the response, the server must include the Access-Control-Allow-Origin response header, typically echoing the value from the request's Origin header (or using a wildcard "*" for broader permissions, though this is restricted when credentials are involved).[20]
In response handling, the browser verifies the Access-Control-Allow-Origin header against the request's origin; if they match exactly or the wildcard applies appropriately, the response body and certain headers become accessible to the web application.[21] If the server omits the header or provides an invalid value, the browser silently blocks access to the response, treating it as a network error without notifying the application, thereby upholding security without exposing details of the failure.[22] This process allows simple requests to proceed efficiently for operations like loading images or submitting forms, while deferring more complex scenarios to preflight mechanisms.[23]
Preflight Requests
Preflight requests serve as a preliminary safeguard in the Cross-Origin Resource Sharing (CORS) mechanism, allowing browsers to verify server permissions before executing potentially unsafe cross-origin operations.[24] These requests are automatically initiated by the user agent when certain conditions indicate a higher risk of unintended access, ensuring that servers explicitly authorize non-standard methods or headers.[25]
A preflight request is triggered for cross-origin requests that do not qualify as simple requests, specifically when the HTTP method is neither GET, HEAD, nor POST, or when the request includes headers outside the set of simple headers such as Cache-Control, Authorization, or others defined in the specification.[26] Additionally, preflight is required if the Content-Type header is set to values like application/xml, application/json, or text/xml, which are not among the simple content types (application/x-www-form-urlencoded, multipart/form-data, or text/plain).[18] Custom author request headers, such as Authorization or X-Requested-With, also necessitate a preflight to prevent unauthorized exposure of sensitive data.[27]
The preflight process begins with the browser sending an HTTP OPTIONS request to the target resource's URL, independent of the actual request method.[24] This OPTIONS request includes three key headers: Origin (indicating the requesting site's origin), Access-Control-Request-Method (specifying the intended method, e.g., PUT or DELETE), and Access-Control-Request-Headers (listing any non-simple custom headers).[28] The server must respond with a successful status (typically 200 OK or 204 No Content) and include CORS-specific response headers to approve the operation: Access-Control-Allow-Origin (matching or allowing the request's origin), Access-Control-Allow-Methods (listing permitted methods, e.g., GET, POST, PUT), and Access-Control-Allow-Headers (authorizing the requested headers).[29] If the server approves, the browser proceeds to send the actual cross-origin request; otherwise, the process halts.[25]
To optimize performance and reduce unnecessary network overhead, preflight responses can be cached by the browser using the Access-Control-Max-Age response header, which specifies the cache duration in seconds.[30] This value indicates how long the results of the preflight—such as allowed methods and headers—remain valid before a new preflight is required for subsequent identical requests from the same origin.[31] Servers set this header to balance security with efficiency, though user agents may impose implementation-specific limits on the maximum age.
In error cases, if the preflight request fails—due to a non-successful HTTP status code (e.g., 4xx or 5xx), missing required CORS headers, or mismatch in allowed origins, methods, or headers—the browser does not send the actual cross-origin request.[23] Instead, it reports a network error to the web application, typically triggering the onerror event in XMLHttpRequest or Fetch API contexts without exposing details of the failure to prevent information leakage.[32] This silent blocking ensures that unauthorized requests cannot proceed, maintaining the integrity of the same-origin policy while allowing controlled cross-origin access.[27]
In Cross-origin resource sharing (CORS), browsers automatically include specific HTTP request headers to facilitate secure cross-origin communication by informing the server about the request's origin and intended characteristics. These headers enable the server to evaluate and respond to the request appropriately, distinguishing between simple and preflight requests.[3]
The Origin header is a fundamental CORS request header sent by the user agent in all cross-origin requests, including both simple and preflight types. It specifies the origin (scheme, host, and port) from which the request originates, such as https://example.com, allowing the server to verify the requesting party's identity before granting access. This header is always included unless the request is same-origin or involves certain opaque types, and it replaces earlier mechanisms like the Origin header defined in the Web Origin Concept.[3][33]
For preflight requests, which use the OPTIONS method to probe server permissions before the actual cross-origin request, the browser includes the Access-Control-Request-Method header. This header declares the HTTP method (e.g., POST or PUT) that the subsequent actual request intends to use, enabling the server to pre-approve or deny it based on policy. It is only sent when the method is not one of the simple ones (GET, HEAD, or POST).[3]
Similarly, the Access-Control-Request-Headers header appears exclusively in preflight requests and lists the custom (author-requested) headers that the actual request will include, separated by commas (e.g., X-Custom-Header, X-Another-Header). Its purpose is to allow the server to assess and authorize these non-standard headers in advance, preventing unauthorized exposure of sensitive data. Simple headers like Accept or Content-Language are excluded from this list, as they do not require pre-approval.[3]
The Access-Control-Request-Private-Network header, introduced in the Private Network Access specification, is sent as true in preflight requests targeting private network addresses (e.g., localhost or 192.168.x.x) from public origins. It alerts the server to the potential security risk of cross-network access, allowing explicit opt-in. As of November 2025, this is supported in major browsers including Chrome 98+ and Firefox 109+.[34]
In simple requests, particularly POST requests, the standard Content-Type header may be included to indicate the media type of the request body, but it is restricted to safe values such as application/x-www-form-urlencoded, multipart/form-data, or text/plain to avoid triggering a preflight. Non-safe types, like application/[json](/page/JSON), necessitate a preflight request.[3]
In Cross-Origin Resource Sharing (CORS), servers respond to cross-origin requests by including specific HTTP headers that authorize access, methods, and headers, thereby overriding the browser's default same-origin restrictions. These response headers are evaluated by the user agent to determine if the response can be accessed by the requesting script, ensuring controlled sharing of resources across origins. For simple requests, the server may include relevant headers directly in the response; for preflight requests, these headers appear in the OPTIONS response to inform subsequent actual requests.
The Access-Control-Allow-Origin header specifies the origins permitted to access the resource. Its value is either a single origin (e.g., https://[example.com](/page/Example.com)), which must exactly match the Origin header sent by the client, or the wildcard * to allow any origin.[22] If the header is absent or the origin does not match, the browser blocks access to the response. The wildcard * provides broad permission but is restricted in certain scenarios for security reasons.
The Access-Control-Allow-Methods header, used primarily in preflight responses, enumerates the HTTP methods (e.g., GET, [POST](/page/Post), PUT) that the server accepts for the actual request. It is a comma-separated list of method names, case-sensitive, and must include any non-simple methods requested via the preflight's Access-Control-Request-Method header.[35] This header ensures that only approved methods can proceed cross-origin.
Similarly, the Access-Control-Allow-Headers header in preflight responses indicates the custom request headers the server permits, mirroring the client's Access-Control-Request-Headers. Its value is a comma-separated list of header names (e.g., X-Custom-Header, Content-Type), excluding simple headers like Accept.[36] If a requested header is not listed, the browser will reject the actual request.
The Access-Control-Max-Age header defines the maximum time, in seconds, that the preflight response can be cached by the browser (e.g., 3600 for one hour). It accepts a non-negative integer, allowing reuse of preflight results to reduce unnecessary OPTIONS requests.[37] Browsers clamp excessive values but honor the duration to optimize performance.
The Access-Control-Allow-Credentials header, when set to true, signals that the response includes credentials such as cookies or authorization headers. It must be a literal true value and cannot be combined with the wildcard * in Access-Control-Allow-Origin; a specific origin is required to prevent unauthorized credential exposure.[38]
The Access-Control-Expose-Headers header, used in actual CORS responses (not preflight), specifies which non-simple response headers the client can access. Its value is a comma-separated list of header names (e.g., X-Total-Count, [Location](/page/Location)) or * to expose all. By default, only simple headers like Cache-Control are exposed; this header allows controlled exposure of custom ones to prevent information leakage.[7]
The Access-Control-Allow-Private-Network header, part of the Private Network Access extension, is set to true in preflight responses to permit requests from public origins to private network resources. It must be present for such requests to succeed, enhancing security by requiring explicit server approval for potentially risky cross-network access. As of November 2025, support is widespread in modern browsers.[39]
Wildcard usage in Access-Control-Allow-Origin as * grants access to any origin but is incompatible with Access-Control-Allow-Credentials: true, as this would risk leaking sensitive data like authentication tokens to untrusted sites. In such cases, servers must specify exact origins to maintain security boundaries.[22] These headers collectively enforce a permission model that balances usability with protection against cross-site request forgery and data exfiltration.
Examples
Simple Request Example
Consider a scenario where a web page hosted at https://www.example.com uses the Fetch API to retrieve JSON data from an API endpoint at https://api.example.com/data. This constitutes a simple cross-origin GET request, as it employs a safe method (GET) without custom request headers or restricted Content-Type values.[40]
The client-side JavaScript code for initiating this request is straightforward, relying on the default Fetch options that align with simple request criteria:
javascript
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Request failed:', error));
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Request failed:', error));
The browser automatically appends the Origin header to the request, specifying Origin: https://www.example.com, to inform the server of the requesting site's origin.[33]
A network trace of this interaction reveals the following exchange:
Request:
GET /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
...
GET /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
...
Response (successful case):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
...
{"message": "Sample data"}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
...
{"message": "Sample data"}
The server must include the Access-Control-Allow-Origin response header matching the request's Origin (or using a wildcard * for broader access) to permit the cross-origin read.[41]
If the response headers align appropriately, the browser delivers the JSON data to the JavaScript, enabling successful processing; otherwise, it blocks access and logs a CORS error in the console, such as "No 'Access-Control-Allow-Origin' header is present on the requested resource." Unlike requests requiring a preflight check, this simple request proceeds directly without an initial OPTIONS probe.[42]
Preflight Request Example
Consider a scenario where a web page hosted at https://www.example.com attempts to send a POST request to an API endpoint at https://api.example.com/update, including a custom Authorization header with a Bearer token. This request triggers a preflight because the Authorization header is not part of the simple CORS-safelisted request headers, and POST is considered potentially unsafe when combined with non-simple headers.[2][13]
The client-side JavaScript using the Fetch API might look like this:
javascript
fetch('https://api.example.com/update', {
method: 'POST',
headers: {
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ data: 'example' })
});
fetch('https://api.example.com/update', {
method: 'POST',
headers: {
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ data: 'example' })
});
This code initiates the cross-origin request, but the browser first sends an OPTIONS preflight request to verify server permissions.[2]
The preflight request trace would include the following headers:
OPTIONS /update HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
OPTIONS /update HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
If the server approves the request, it responds with a 204 No Content status and the necessary CORS allowance headers, such as:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: authorization
Access-Control-Max-Age: 86400
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: authorization
Access-Control-Max-Age: 86400
The Access-Control-Max-Age header indicates how long (in seconds) the preflight results can be cached by the browser, reducing subsequent preflight overhead.[2][13]
Upon successful preflight, the browser proceeds with the actual POST request:
[POST](/page/Post-) /update HTTP/1.1
Host: api.example.com
[Origin](/page/Origin): https://www.example.com
[Authorization](/page/Authorization): Bearer token123
Content-Type: application/[json](/page/JSON)
{"data": "example"}
[POST](/page/Post-) /update HTTP/1.1
Host: api.example.com
[Origin](/page/Origin): https://www.example.com
[Authorization](/page/Authorization): Bearer token123
Content-Type: application/[json](/page/JSON)
{"data": "example"}
The server then responds to the POST, including the Access-Control-Allow-Origin header to permit the client to read the response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
{"status": "success"}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
{"status": "success"}
If the preflight succeeds, the POST request proceeds as normal, allowing the client to process the response. However, if the server rejects the preflight—such as by omitting required allowance headers or returning an error status—the browser blocks the actual request, preventing any cross-origin access and logging a CORS error in the console.[2][13]
Implementation
Server-Side Configuration
Server-side configuration for Cross-Origin Resource Sharing (CORS) involves inspecting the Origin header in incoming requests and responding with appropriate Access-Control-* headers to indicate which origins are permitted to access the resource. This mechanism allows servers to relax the same-origin policy selectively, enabling cross-origin requests while maintaining security boundaries. The server must evaluate the request's origin against a predefined allowlist or policy before setting headers like Access-Control-Allow-Origin.
In practice, implementations vary by web server or framework, often using middleware or configuration directives to automate header addition. For Node.js with Express, the cors middleware package simplifies this by handling origin validation and header injection. A basic setup permits requests only from a specific origin, such as:
javascript
const express = require('express');
const cors = require('cors');
const app = express();
const corsOptions = {
origin: 'https://example.com'
};
app.use(cors(corsOptions));
const express = require('express');
const cors = require('cors');
const app = express();
const corsOptions = {
origin: 'https://example.com'
};
app.use(cors(corsOptions));
This configuration checks the Origin header against the specified value and echoes it in Access-Control-Allow-Origin if valid; otherwise, it rejects the request.[43]
For Apache HTTP Server, CORS headers can be added via .htaccess files or the main configuration, using the mod_headers module. To allow all origins (not recommended for production due to security risks), include:
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type"
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type"
These directives apply to the directory where the .htaccess file resides, ensuring OPTIONS preflight requests receive appropriate responses. More restrictive setups limit origins to specific domains, e.g., Header always set Access-Control-Allow-Origin "https://example.com".
Nginx configurations achieve similar results through server blocks in nginx.conf or site-specific files. A common setup for enabling CORS across all origins includes:
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
This handles preflight OPTIONS requests explicitly and adds headers to other methods, with the if block ensuring efficient processing.
For dynamic origin validation, servers can implement logic to check the Origin header against runtime conditions, such as environment variables or databases, rather than static lists. In Express, this uses a callback function in the cors options:
javascript
const corsOptions = {
origin: [function](/page/Function) (origin, callback) {
const allowedOrigins = ['https://example.com', 'https://another.com'];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
const corsOptions = {
origin: [function](/page/Function) (origin, callback) {
const allowedOrigins = ['https://example.com', 'https://another.com'];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
This approach echoes the validated Origin in the response header, enhancing flexibility for multi-tenant applications while preventing unauthorized access.[43]
If no CORS headers are sent in response to a cross-origin request, the browser treats it as an opaque response, which provides no access to the content, status, or headers from client-side JavaScript. Opaque responses occur in "no-cors" mode or when the server omits Access-Control-Allow-Origin, limiting the response to metadata like size for caching purposes but blocking content reading to enforce security. This default behavior ensures that unconfigured servers do not inadvertently expose resources cross-origin.[44]
Handling Credentials and Security
To enable the transmission of credentials such as cookies, HTTP authentication details, and TLS client certificates in cross-origin requests, the client must configure the request accordingly, while the server enforces specific response headers for security. On the client side, developers set the credentials mode to "include" using the Fetch API, as in fetch(url, { credentials: 'include' }), or enable it via XMLHttpRequest.withCredentials = true for XMLHttpRequest objects; by default, credentials are omitted to prevent unintended data leakage.[45][2] The server responds by including the Access-Control-Allow-Credentials: true header, which signals to the browser that credentials are permitted, but this requires the Access-Control-Allow-Origin header to specify an exact origin (e.g., [https](/page/HTTPS)://example.com) rather than the wildcard *, as wildcards are forbidden in credentialed scenarios to avoid broad exposure of sensitive data.[38]
These configurations have direct implications for cross-origin authentication and session management. When credentials mode is active and the server allows it, the browser includes user-specific data like session cookies or basic authentication headers in the request, enabling secure, stateful interactions such as logged-in API calls from third-party web applications. Similarly, TLS client certificates can be leveraged for mutual authentication across origins, though this depends on the browser's handling of certificate stores. However, third-party cookie restrictions in modern browsers (e.g., SameSite policies) may still block such inclusions independently of CORS settings, emphasizing that CORS alone does not override all credential protections.[13][2]
For basic security in credentialed CORS setups, servers must implement the Vary: Origin response header alongside origin-specific Access-Control-Allow-Origin to mitigate caching attacks, where a shared cache might incorrectly serve one origin's credentialed response to another, potentially exposing private data. Validation of the incoming Origin header against a predefined allowlist of trusted domains is essential, ensuring only authorized origins receive permissive responses and preventing malicious sites from proxying requests.[2]
In common API implementations, credentialed CORS is typically restricted to a narrow set of trusted domains, such as an application's frontend domain, to balance usability with security; for instance, a backend server might dynamically echo the validated Origin in Access-Control-Allow-Origin only for origins in an allowlist like https://app.example.com or https://admin.example.com, rejecting all others to limit attack surfaces.[46]
Compatibility and Adoption
Browser Support
Cross-origin resource sharing (CORS) has seen broad browser adoption since its initial implementations in the late 2000s, with partial support appearing first in Internet Explorer 8 through the proprietary XDomainRequest API, which provided limited cross-origin capabilities but lacked full CORS compliance, such as support for standard XMLHttpRequest or custom headers.[47][48] Full CORS support arrived in Internet Explorer 10 with partial implementation and in Internet Explorer 11 with complete support, while Microsoft Edge from version 12 onward offered full compatibility.[47] Chrome provided partial support from version 4 (2008) to 12, achieving full support by version 13 (2011); Firefox from version 3.5 (2009); Safari with partial support in versions 4 to 5.1 (2009–2011) and full from version 6 (2012).[47]
Feature-specific implementations include preflight requests, which are supported in all modern browsers since around 2010, enabling the handling of non-simple requests via OPTIONS method checks.[2] Credentials support, allowing inclusion of cookies and authorization headers via the Access-Control-Allow-Credentials header, aligns closely with basic CORS timelines: full in Chrome 4+, Firefox 3.5+, Safari 4+, Edge 12+, and Internet Explorer 11+, though older Android browsers (pre-4.4) exhibited variations, such as stricter handling of TLS client certificates or redirects.[49][38]
| Browser | Basic CORS Support | Preflight Support | Credentials Support |
|---|
| Chrome | Partial (4–12), Full (13+) | Full (13+) | Full (4+) |
| Firefox | Full (3.5+) | Full (3.5+) | Full (3.5+) |
| Safari | Partial (4–5.1), Full (6+) | Full (6+) | Full (4+) |
| Edge | Full (12+) | Full (12+) | Full (12+) |
| Internet Explorer | Partial (8–10 via XDomainRequest), Full (11) | Partial (10+), Full (11) | Full (11) |
As of 2025, CORS enjoys universal support across browsers holding more than 1% global market share, with no significant compatibility gaps in current versions; tools like Can I use... confirm coverage exceeding 98% worldwide, reflecting its status as a foundational web standard.[47][2]
Specification History
Cross-Origin Resource Sharing (CORS) originated as a response to limitations in the same-origin policy for enabling secure cross-origin HTTP requests. The foundational concept was proposed by the W3C Voice Browser Working Group in 2005 as an XML processing instruction for cross-origin access in voice applications, which was later generalized to an HTTP header-based mechanism.[50] This evolution involved contributions from browser vendors, including Mozilla, which implemented early support in Firefox 3.5 in 2009.[2]
The W3C published the first Working Draft of the CORS specification on March 17, 2009, defining the protocol for client-side cross-origin requests using headers like Access-Control-Allow-Origin.[51] Subsequent drafts followed, with the Last Call Working Draft released on April 3, 2012, and the specification advancing to Candidate Recommendation status on January 29, 2013.[52] The W3C formalized CORS as a Recommendation on January 16, 2014, establishing it as a stable standard integrated with HTML5 and the XMLHttpRequest Level 2 specification, which it influenced by providing the underlying protocol for cross-origin XHR support.[3]
Since 2014, CORS has been maintained as part of the WHATWG Fetch Living Standard, with the core mechanism last undergoing major updates in that year.[53] The Fetch API, introduced in 2015, enhanced CORS usage by providing a modern, promise-based interface for cross-origin fetches.[54] Minor clarifications and extensions have occurred through 2025, including adjustments for edge cases like private network access via the Private Network Access specification, which extends CORS preflights to prevent unintended exposure of local resources.[55] The standard, often referred to as CORS 1.0 in its initial form, includes unused extensions in the Fetch specification such as report-only modes for testing configurations without enforcement.[53] CORS effectively supersedes older ad-hoc mechanisms for cross-origin data sharing, offering a standardized, server-controlled alternative.[3]
Security and Alternatives
Security Considerations
Misconfigured Cross-Origin Resource Sharing (CORS) can introduce significant security vulnerabilities by inadvertently exposing sensitive resources to unauthorized origins. One primary risk arises from using the wildcard (*) in the Access-Control-Allow-Origin header, which permits any domain to access the resource, potentially allowing malicious websites to read private data or perform unauthorized actions on behalf of the user.[56][57] This configuration is particularly dangerous in internal networks, where it can enable external attackers to access intranet resources via subdomain tricks like intranet.evil-site.com.[57]
Another concern involves credential handling, where enabling Access-Control-Allow-Credentials: true without restricting origins can lead to credential leaks, especially in conjunction with cross-site request forgery (CSRF) attacks. If CORS allows credentialed requests from untrusted origins, attackers may exploit this to steal session cookies or CSRF tokens by tricking users into loading malicious pages that issue cross-origin requests.[57][58] The W3C specification prohibits combining wildcards with credentials to mitigate this, but improper implementations can still expose data if safe HTTP methods (like GET or HEAD) are not enforced for credentialed endpoints.[56]
Origin spoofing attempts, while limited by browsers that automatically set the Origin header and prevent user overrides, often succeed through reflection misconfigurations. Servers that blindly echo the Origin header without validation can be bypassed using crafted subdomains (e.g., trusted-site.attacker.com), granting access to otherwise protected resources.[57][58] Whitelisting null origins is similarly exploitable via sandboxed iframes, allowing attackers to impersonate trusted contexts.[57]
Recent developments highlight ongoing risks in CORS implementations. In 2021, the Private Network Access specification extended CORS with preflight requests to protect private IP ranges (e.g., 192.168.0.0/16) from public websites, requiring explicit server approval via new headers like Access-Control-Allow-Private-Network: true to prevent CSRF-like attacks on local devices such as routers.[59] In 2025, multiple Common Vulnerabilities and Exposures (CVEs) exposed overly permissive CORS in libraries and applications; for instance, CVE-2024-8487 in modelscope/agentscope v0.0.4 allowed any external domain to access APIs, risking data disclosure and system compromise (CVSS 9.8).[60] Similarly, CVE-2024-8024 in netease-youdao/qanything 1.4.1 enabled Same-Origin Policy bypasses, potentially leaking sensitive information (CVSS 7.5).[61] Later in 2025, CVE-2025-8036 revealed a browser-side vulnerability in major browsers (Chromium-based, Edge, Safari, Firefox) where CORS policies could be bypassed via DNS rebinding attacks, enabling unauthorized access to services on arbitrary ports and exposing private networks; users are advised to update browsers promptly (disclosed October 2025).[62] Additionally, the OWASP Top 10 for 2025 lists CORS misconfigurations as a key example under A01: Broken Access Control, emphasizing their role in enabling unauthorized API access.[63]
To mitigate these risks, administrators should adopt strict best practices, such as specifying exact trusted origins in Access-Control-Allow-Origin (e.g., https://[example.com](/page/Example.com)) rather than wildcards, and enforcing HTTPS for all endpoints to prevent man-in-the-middle interception of requests.[56][58] Rate limiting preflight OPTIONS requests helps thwart denial-of-service attempts that probe for misconfigurations, while avoiding exposure of sensitive endpoints via CORS altogether—opting instead for server-side authentication—further reduces the attack surface.[57]
Auditing CORS configurations is essential for identifying vulnerabilities; tools like OWASP ZAP can intercept headers to detect wildcards, reflected origins, or invalid credential settings, enabling manual testing of cross-origin requests from simulated malicious domains.[56] Regular scans with such tools, combined with validation of origin whitelists using exact string matching, ensure robust protection against exploitation.[58]
Comparison with JSONP
JSONP, or JSON with Padding, is a legacy workaround for the same-origin policy (SOP) that enables cross-origin data retrieval by dynamically injecting <script> elements into the page. The client specifies a callback function name as a query parameter (e.g., ?callback=handleData), and the server responds by wrapping the JSON payload in that function call (e.g., handleData({ "data": "value" })), which executes immediately upon loading. This approach leverages the browser's permission to load scripts from any origin but is inherently restricted to GET requests and provides no native error handling, as failed loads simply do not execute the callback.[64][65]
Despite its simplicity, JSONP suffers from several critical limitations and risks. It exclusively supports GET methods, precluding POST, PUT, or other verbs essential for many API interactions, and cannot include custom headers or request bodies. Browser caching of script tags can also result in outdated responses without reliable invalidation mechanisms. More alarmingly, JSONP introduces severe security vulnerabilities, including cross-site scripting (XSS) through unsanitized callback parameters that allow attackers to inject executable code, and cross-site script inclusion (XSSI) exploits that enable leakage of sensitive information like API keys or user data across origins.[64][65][66]
CORS surpasses JSONP by offering a standardized, server-controlled protocol that accommodates all HTTP methods, custom headers, and request bodies while providing robust error reporting via HTTP status codes and the ability to include credentials such as cookies or authorization tokens. Unlike JSONP's script-based execution, CORS integrates seamlessly with modern APIs like fetch() and XMLHttpRequest, ensuring safer and more flexible cross-origin access. Following CORS's elevation to W3C Recommendation in January 2014 and near-universal browser adoption, JSONP has been progressively deprecated since approximately 2015, rendering it obsolete for contemporary development.[52][65]
To migrate JSONP endpoints to CORS, developers configure servers to emit headers like Access-Control-Allow-Origin and Access-Control-Allow-Methods in responses, eliminating the need for callback wrapping and enabling direct use of standard AJAX techniques. This transition enhances security and maintainability, and JSONP is strongly discouraged for new implementations due to its outdated nature and persistent risks.[67][65]