Skip to content
Feb 28

CORS and Cross-Origin Requests

MT
Mindli Team

AI-Generated Content

CORS and Cross-Origin Requests

In today's web architecture, applications are routinely split between frontend clients and backend APIs hosted on separate domains. CORS, or Cross-Origin Resource Sharing, is the critical security mechanism that governs how web pages can safely make requests to different domains. Without it, the browser's Same-Origin Policy would block all such communication, breaking modern distributed web apps. Understanding CORS allows you to configure secure, functional access between your JavaScript frontends and your APIs.

The Same-Origin Policy and the Rationale for CORS

Browsers enforce a fundamental security rule called the Same-Origin Policy. This policy restricts how a document or script loaded from one origin (defined by the combination of protocol, domain, and port) can interact with resources from another origin. For instance, JavaScript on https://www.example.com cannot, by default, make an API request to https://api.othersite.com. This prevents malicious sites from reading sensitive data from other tabs or sessions. However, legitimate use cases abound—your React app on a CDN needs to talk to your backend API. CORS provides a standardized way for servers to declare which cross-origin requests are permitted, giving browsers the rules to allow or deny them.

Simple Requests Versus Preflighted Requests

CORS distinguishes between two types of cross-origin requests: simple and preflighted. A simple request is one that meets specific, restrictive criteria: it uses only the methods GET, HEAD, or POST; its headers are limited to a safe set including Accept, Accept-Language, and Content-Type (where Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain); and it doesn't use custom headers. For simple requests, the browser sends the request directly and checks the CORS headers in the response.

Any request that falls outside these criteria is a non-simple request. For these, the browser sends a preflight OPTIONS request before the actual request. This preflight is an HTTP OPTIONS request that asks the server for permission. The browser sends headers like Access-Control-Request-Method and Access-Control-Request-Headers to specify what the actual request intends to do. The server must respond appropriately to this preflight before the browser proceeds with the main request. This two-step process allows servers to explicitly approve or reject potentially unsafe operations like PUT methods or custom headers.

Server-Side CORS Headers and Configuration

The server controls CORS behavior through specific HTTP response headers. When a browser makes a cross-origin request, it looks for these headers to determine if access is allowed. The most fundamental is Access-Control-Allow-Origin. This header specifies which origins are permitted to access the resource. For example, Access-Control-Allow-Origin: https://www.example.com allows only that specific origin, while a wildcard * allows any origin (with important limitations, as discussed later).

For preflighted requests, the server's response to the OPTIONS request must include additional headers. Access-Control-Allow-Methods lists the HTTP methods (e.g., GET, POST, PUT) the server accepts for the actual request. Access-Control-Allow-Headers lists the custom headers that the client is allowed to send. The server may also include Access-Control-Max-Age to cache the preflight response, improving performance. When the actual request is sent, the browser again checks the Access-Control-Allow-Origin header in the response. Proper configuration of these headers is what enables secure cross-origin API access.

Advanced CORS: Credentials and Wildcard Limitations

Handling credentials like cookies or HTTP authentication adds complexity. By default, browsers do not send credentials with cross-origin requests. To include them, the client must set the withCredentials property to true in the XMLHttpRequest or Fetch API call. On the server side, this requires two specific responses: the Access-Control-Allow-Origin header must specify an exact origin—it cannot be a wildcard *—and the server must send the Access-Control-Allow-Credentials: true header. This restriction ensures that a resource allowing credentials cannot be broadly accessible to any origin, which would be a significant security risk.

The wildcard limitation is a key security feature. While Access-Control-Allow-Origin: * is convenient for public APIs, it is incompatible with credentialed requests. Furthermore, wildcards are not allowed in Access-Control-Allow-Headers or Access-Control-Allow-Methods for requests that require credentials. You must explicitly list the allowed origins, methods, and headers. This explicit enumeration is a best practice as it minimizes the attack surface and prevents misconfiguration that could expose your API to unintended domains.

Implementing Secure Cross-Origin API Access

Configuring CORS securely involves a deliberate approach. First, audit your application to determine which origins truly need access—avoid using the wildcard * in production unless your API is purely public and non-authenticated. In your server-side code or gateway (e.g., using middleware in Express.js or configuration in Nginx), programmatically set the CORS headers based on a whitelist of allowed origins. For dynamic whitelisting, you can check the Origin header from the incoming request against your list and echo it back in the Access-Control-Allow-Origin response if it's allowed.

Always handle preflight requests correctly by responding to OPTIONS methods with the appropriate Access-Control-Allow-Methods and Access-Control-Allow-Headers. If your API uses credentials, remember the strict rule: no wildcards with Access-Control-Allow-Credentials. Test your configuration thoroughly using browser developer tools to inspect the network traffic, ensuring preflight requests succeed and headers are correctly set. This process ensures that your cross-origin resource sharing is both functional and aligned with security best practices.

Common Pitfalls

  1. Omitting the Preflight Response: A frequent mistake is configuring CORS headers for the main request but forgetting to handle the OPTIONS preflight request. If your server doesn't respond to OPTIONS with the correct CORS headers, the browser will block the actual request. Correction: Ensure your server or web framework has a route or middleware that responds to HTTP OPTIONS methods with the necessary Access-Control-Allow-* headers.
  1. Using Wildcards with Credentials: Setting Access-Control-Allow-Origin: * while also trying to send cookies will always fail. The browser will block the request. Correction: When using withCredentials on the client, configure your server to dynamically reflect the validated Origin request header in the Access-Control-Allow-Origin response and include Access-Control-Allow-Credentials: true.
  1. Overly Permissive Headers: Specifying Access-Control-Allow-Headers: * or listing unnecessary headers can increase security risks. Correction: Explicitly list only the headers your application needs, such as Content-Type or Authorization. This follows the principle of least privilege.
  1. Ignoring CORS in Error Responses: Servers often send proper CORS headers for successful (2xx) responses but forget to include them on error (4xx, 5xx) responses. The browser still checks CORS headers on all cross-origin responses, so a missing header on an error can cause the client to see a network error instead of the actual HTTP status. Correction: Apply your CORS middleware or headers to all HTTP responses, regardless of status code.

Summary

  • CORS is a browser-enforced security protocol that allows servers to specify which cross-origin requests are permitted, relaxing the strict Same-Origin Policy for legitimate needs.
  • Browsers use preflight OPTIONS requests for non-simple requests (those with custom headers, non-standard methods, etc.) to obtain server permission before sending the actual request.
  • Servers control access through specific headers like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers, which must be configured precisely.
  • Credentialed requests require explicit origins; the wildcard * cannot be used with Access-Control-Allow-Credentials: true, mandating a strict whitelist approach.
  • Secure configuration involves whitelisting specific origins, explicitly allowing needed methods and headers, and ensuring CORS headers are present on all responses, including errors.
  • Mastery of simple versus preflighted requests and these configuration rules enables you to build web applications that securely integrate APIs across different domains.

Write better notes with AI

Mindli helps you capture, organize, and master any subject with AI-powered summaries and flashcards.