Skip to content

HTML5 - Web CORS

HTML5 - Cross-Origin Resource Sharing (CORS)

Section titled “HTML5 - Cross-Origin Resource Sharing (CORS)”

Cross-Origin Resource Sharing (CORS) is a security mechanism implemented by web browsers that controls how web pages in one origin (domain, protocol, and port) can request and access resources from a server located at a different origin.

By default, browsers enforce the Same-Origin Policy (SOP), which restricts web pages from making requests to a different domain than the one that served the web page. This prevents malicious scripts on one site from reading sensitive data from another site the user might be logged into.

CORS provides a controlled way for servers to relax the SOP and explicitly allow requests from specific origins (or all origins), enabling legitimate cross-origin interactions like fetching data from a public API hosted on a different domain.

CORS works by adding new HTTP headers to the communication between the browser and the server:

  1. Browser Request: When a script on https://client.com tries to make a cross-origin request (e.g., using fetch or XMLHttpRequest) to https://api.server.com, the browser automatically adds an Origin header to the HTTP request: Origin: https://client.com.
  2. Server Response: The server at https://api.server.com inspects the Origin header. If the server is configured to allow requests from https://client.com, it includes specific CORS headers in its response. The most important one is Access-Control-Allow-Origin.
  3. Browser Check: The browser receives the response. It checks if the Access-Control-Allow-Origin header is present and if its value matches the origin of the requesting script (https://client.com) or is the wildcard (*). If the origin is allowed, the browser allows the script to access the response data. If not, the browser blocks the response from the script and generates a CORS error in the console.

CORS distinguishes between ‘simple’ requests and those that require a ‘preflight’ check:

A request is considered ‘simple’ if it meets all the following conditions:

  • Method is GET, HEAD, or POST.
  • Headers only include ‘simple’ headers like Accept, Accept-Language, Content-Language, Content-Type (with specific values like application/x-www-form-urlencoded, multipart/form-data, text/plain), and a few others.
  • No event listeners are registered on any XMLHttpRequestUpload object.

For simple requests, the browser makes the actual request directly and checks the Access-Control-Allow-Origin header in the response.

Requests that do not meet the ‘simple’ criteria (e.g., use methods like PUT, DELETE, PATCH, or include custom headers like Authorization or Content-Type: application/json) require a preflight request.

Before sending the actual request, the browser automatically sends an OPTIONS request to the server URL. This OPTIONS request includes headers indicating the method (Access-Control-Request-Method) and any custom headers (Access-Control-Request-Headers) the actual request intends to use.

The server responds to the OPTIONS request with headers like:

  • Access-Control-Allow-Origin: Specifies the allowed origin(s).
  • Access-Control-Allow-Methods: Lists the HTTP methods allowed for the actual request (e.g., GET, POST, PUT, DELETE).
  • Access-Control-Allow-Headers: Lists the custom headers allowed for the actual request.
  • Access-Control-Max-Age: Specifies how long (in seconds) the results of the preflight request can be cached.

If the preflight response indicates that the actual request is allowed (origin, method, headers match), the browser then proceeds to send the actual request (e.g., the PUT request with Content-Type: application/json). If the preflight fails, the actual request is not sent, and a CORS error occurs.

Making a CORS Request (Client-Side Example using Fetch)

Section titled “Making a CORS Request (Client-Side Example using Fetch)”

Modern JavaScript typically uses the fetch API for making HTTP requests. Browsers handle adding the Origin header and performing preflight checks automatically.

const apiUrl = 'https://api.example.com/data'; // A cross-origin API endpoint
async function fetchData() {
console.log(`Fetching data from ${apiUrl}...`);
try {
const response = await fetch(apiUrl, {
method: 'GET', // Simple request (if no custom headers)
// For preflighted requests, add headers etc:
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer your_token'
// },
// body: JSON.stringify({ key: 'value' })
});
// Check if the request was successful (status code 200-299)
if (!response.ok) {
// Check if the error might be CORS-related (opaque response)
if (response.type === 'opaque') {
console.error('Request failed. This might be a CORS issue or network error. Check browser console.');
} else {
// Server returned an error status code
console.error(`Server error: ${response.status} ${response.statusText}`);
}
// Throw an error to be caught by the catch block
throw new Error(`HTTP error! status: ${response.status}`);
}
// If response is OK, parse the data (e.g., as JSON)
const data = await response.json();
console.log('Data received successfully:', data);
// Process the data...
} catch (error) {
// Handle network errors or errors thrown above
console.error('Fetch error:', error);
// Display error message to the user
}
}
fetchData();

Important: If the server doesn’t send the correct CORS headers (Access-Control-Allow-Origin), the fetch promise will not reject immediately due to a CORS error. Instead, the browser prevents the script from accessing the response, often resulting in an opaque response (response.type === 'opaque') or a generic network error in the catch block. The detailed CORS error message will typically only appear in the browser’s developer console.

Configuring CORS is done on the server. The exact implementation depends on the server-side language, framework, or web server software (Apache, Nginx) being used.

Typically, server configuration involves:

  • Checking the Origin header of incoming requests.
  • Adding the Access-Control-Allow-Origin header to responses, setting its value to the specific allowed origin(s) (e.g., https://client.com) or * (allows any origin - use with caution, especially for authenticated APIs).
  • For preflight (OPTIONS) requests, responding with Access-Control-Allow-Methods, Access-Control-Allow-Headers, and optionally Access-Control-Max-Age.

Many web frameworks provide middleware or plugins to simplify CORS configuration.

If you encounter CORS issues, check the browser’s developer console for messages like:

  • No 'Access-Control-Allow-Origin' header is present on the requested resource. (Most common error - server didn’t allow the origin).
  • The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin. (Server allowed a different origin).
  • Request header field ... is not allowed by Access-Control-Allow-Headers in preflight response. (Preflight didn’t permit a header).
  • Method ... is not allowed by Access-Control-Allow-Methods in preflight response. (Preflight didn’t permit the HTTP method).

Debugging CORS often involves checking both the client-side request details and, crucially, the server-side CORS configuration and response headers.