Back to Blogs

Blog

CORS Explained: What is CORS in Web API and How It Works?

written by
Dhayalan Subramanian
Associate Director - Product Growth at DigitalAPI

Updated on: 

TL;DR

1. CORS (Cross-Origin Resource Sharing) is a browser security feature enabling secure cross-domain data exchange between web applications and APIs, overriding the default Same-Origin Policy.

2. It works by adding specific HTTP headers to API responses, signaling to the browser that requested cross-origin access is permitted.

3. Simple requests bypass preflight checks, while "complex" requests (e.g., non-standard methods, custom headers) trigger an `OPTIONS` preflight request for server approval before the actual request.

4. Key CORS response headers like `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` are crucial for granting explicit permissions.

5. Proper CORS configuration is vital for API security, preventing unauthorized access and ensuring your web API functions reliably across different domains.

Have you ever tried to fetch data from a web API, only to be met with a frustrating error message about "cross-origin" issues? This common hurdle is where CORS, or Cross-Origin Resource Sharing, steps in. It's a fundamental security mechanism for web browsers, designed to protect users from malicious websites, yet often a source of confusion for developers. Understanding what is CORS in Web API development and how it operates is crucial for building modern web applications that seamlessly interact across different domains. This guide will demystify CORS, explaining its underlying principles, how it facilitates secure cross-origin communication, and best practices for configuring it correctly.

What is CORS in Web API?

CORS, or Cross-Origin Resource Sharing, is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. At its core, CORS is a security feature implemented by web browsers to enforce the Same-Origin Policy (SOP). Without CORS, browsers would strictly block any request from a web page on one domain to an API on a different domain, regardless of the API's intent. This default restriction is in place to prevent a malicious website from making unauthorized requests to an API on which a user might be authenticated (e.g., your bank's API).

When a web application (the "client") running on one origin tries to access resources from a web API hosted on a different origin, the browser initiates a cross-origin request. For this request to succeed, the API server must explicitly grant permission using specific CORS response headers. If these headers are absent or don't match the client's origin, the browser will block the response, preventing the web application from accessing the data. Therefore, understanding what is CORS in Web API design is paramount for ensuring interoperability and security.

Understanding the Same-Origin Policy (SOP)

To truly grasp CORS, we first need to understand the browser's fundamental security principle: the Same-Origin Policy (SOP). The SOP is a critical security mechanism that dictates how documents or scripts loaded from one origin can interact with resources from another origin. An "origin" is defined by the combination of scheme (protocol, e.g., HTTP, HTTPS), host (domain name, e.g., example.com), and port (e.g., 80, 443).

For instance, if a web page at `https://mywebapp.com:8080` attempts to make an HTTP request to `https://mywebapp.com:8080/api/data`, this is considered a "same-origin" request and is permitted by default. However, if the same web page tries to fetch data from `https://api.thirdparty.com/data` (different host), `http://mywebapp.com:8080/api/data` (different scheme), or `https://mywebapp.com:9000/api/data` (different port), these are all "cross-origin" requests.

By default, the SOP strictly prohibits cross-origin read access to resources. This prevents a malicious script loaded from one website from reading sensitive data (like authentication tokens or private user information) that a user might have on another website. While essential for security, this restriction also blocks legitimate cross-origin interactions, which is precisely the problem CORS aims to solve by providing a controlled way to relax the SOP's restrictions.

The Problem CORS Solves: Cross-Origin Requests

Modern web development frequently involves scenarios where a web application needs to consume data or services from a different origin. Consider a single-page application (SPA) built with React or Angular, hosted on `https://frontend.com`. This SPA often needs to interact with a backend API design running on a separate server, perhaps `https://api.backend.com`. Without CORS, the browser's Same-Origin Policy would prevent `https://frontend.com` from making AJAX requests to `https://api.backend.com`, leading to immediate security errors in the browser console.

Other common scenarios where CORS is indispensable include:

  • Third-party Integrations: A website embedding widgets or content from external services.
  • Microservices Architectures: Where different services (e.g., user service, product service) might reside on distinct subdomains or ports.
  • API Gateways: When an API gateway sits in front of backend services, potentially on a different domain than the client application.

CORS provides a standardized, secure way for the server to declare which cross-origin requests are permissible, giving developers the flexibility needed for distributed web applications while maintaining essential browser security. It acts as a controlled gatekeeper, allowing legitimate cross-origin communication while still thwarting unauthorized attempts.

How CORS Works: A Step-by-Step Breakdown

CORS operates through a series of HTTP headers exchanged between the client (browser) and the server (web API). The process differs slightly depending on whether the request is considered a "simple request" or a "preflighted request."

Simple Requests

Certain types of cross-origin requests are considered "simple" by the browser and do not trigger a preflight OPTIONS request. A request is simple if it meets ALL of the following conditions:

  1. The HTTP method is one of: `GET`, `HEAD`, `POST`.
  2. The only headers that are manually set are those that are "CORS-safelisted request-headers" (e.g., `Accept`, `Accept-Language`, `Content-Language`, `Content-Type` with values `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`).
  3. No event listeners are registered on any `XMLHttpRequestUpload` object used in the request.
  4. No `ReadableStream` object is used in the request.

For simple requests, the process is straightforward:

  • Client Request: The browser sends the cross-origin request directly to the API, automatically adding an `Origin` header that indicates the domain of the web page making the request (e.g., `Origin: https://frontend.com`).
  • Server Response: The API server receives the request. If it's configured to allow requests from the client's origin, it includes an `Access-Control-Allow-Origin` header in its response (e.g., `Access-Control-Allow-Origin: https://frontend.com` or `Access-Control-Allow-Origin: *` for any origin).
  • Browser Check: The browser inspects the `Access-Control-Allow-Origin` header. If the header's value matches the client's `Origin` (or is `*`), the browser allows the web application to access the response. Otherwise, it blocks the response and throws a CORS error.

Preflight Requests (Complex Requests)

If a cross-origin request does not meet the "simple request" criteria, the browser considers it a "complex request" and automatically triggers a "preflight request." This preflight is an `OPTIONS` HTTP request sent by the browser before the actual request. Its purpose is to ask the API server for explicit permission to make the actual, more complex, cross-origin request.

A preflight request is typically sent if:

  • The HTTP method is not `GET`, `HEAD`, or `POST` (e.g., `PUT`, `DELETE`, `PATCH`).
  • The request includes custom headers (e.g., `X-Custom-Header`, `Authorization` for an API authentication token).
  • The `Content-Type` header is not one of the allowed values for simple requests (e.g., `application/json`).

The preflight process works as follows:

  1. Preflight Request (Browser to Server): The browser sends an `OPTIONS` request to the API's URL. This request includes specific headers to inform the server about the intended actual request:
    • `Origin`: The client's domain.
    • `Access-Control-Request-Method`: The HTTP method of the actual request (e.g., `PUT`).
    • `Access-Control-Request-Headers`: A comma-separated list of any non-safelisted headers the actual request will use (e.g., `X-API-Key, Content-Type`).
  2. Preflight Response (Server to Browser): The API server receives the `OPTIONS` request. If it allows the intended actual request, it responds with headers indicating its permission:
    • `Access-Control-Allow-Origin`: Specifies which origins are allowed (e.g., `https://frontend.com`).
    • `Access-Control-Allow-Methods`: Lists the HTTP methods the server allows (e.g., `GET, POST, PUT, DELETE`).
    • `Access-Control-Allow-Headers`: Lists the non-safelisted headers the server allows (e.g., `X-API-Key, Content-Type`).
    • `Access-Control-Max-Age`: (Optional) Indicates how long the preflight response can be cached, avoiding repeated `OPTIONS` requests for the same resource.
  3. Browser Check & Actual Request: The browser evaluates the preflight response. If all permissions are granted (origin, method, headers), the browser proceeds to send the actual cross-origin request to the API. If any permission is denied, the browser blocks the actual request and reports a CORS error.
  4. Actual Request & Response: If the preflight passes, the actual request is sent, and the API responds with the requested data and potentially an `Access-Control-Allow-Origin` header again. The browser performs a final check on this origin header.

This two-step handshake for complex requests ensures that servers have the opportunity to authorize cross-origin interactions before potentially state-changing requests are even made, adding an extra layer of API security.

CORS Headers Explained in Detail

Understanding the specific CORS headers is key to proper configuration:

  • `Origin` (Request Header): Sent by the browser, indicating the origin of the resource that initiated the request. Example: `Origin: https://www.example.com`.
  • `Access-Control-Allow-Origin` (Response Header): Sent by the server, indicating which origins are allowed to access the resource. Its value can be a specific origin (e.g., `https://www.example.com`), or a wildcard (`*`) to allow any origin (use with caution). This header is mandatory for allowing cross-origin access.
  • `Access-Control-Allow-Methods` (Response Header - Preflight): Sent by the server in response to an `OPTIONS` preflight request, listing the HTTP methods permitted for the actual request (e.g., `GET, POST, PUT, DELETE`).
  • `Access-Control-Allow-Headers` (Response Header - Preflight): Sent by the server in response to an `OPTIONS` preflight request, listing the non-safelisted HTTP headers that are permitted in the actual request (e.g., `Content-Type, Authorization, X-Requested-With`).
  • `Access-Control-Expose-Headers` (Response Header): Allows the server to specify headers, other than the CORS-safelisted response headers, that clients are allowed to access. By default, JavaScript can only access a few simple response headers.
  • `Access-Control-Max-Age` (Response Header - Preflight): Sent by the server in response to an `OPTIONS` preflight request, indicating for how many seconds the results of the preflight request can be cached by the browser. This reduces the number of preflight requests.
  • `Access-Control-Allow-Credentials` (Response Header): If included, indicates that the client is allowed to send credentials (cookies, HTTP authentication, client-side SSL certificates) with the cross-origin request. When this header is present, `Access-Control-Allow-Origin` cannot be `*`; it must be a specific origin.

Configuring CORS on Your Web API

Implementing CORS correctly on your web API is vital for its functionality and security. The configuration method varies depending on your chosen backend framework or server. Here's a general overview:

  • Server-Side Configuration: Most modern web frameworks provide built-in middleware or libraries to handle CORS configuration easily.
    • Node.js (Express.js): The popular `cors` middleware simplifies setup. You can specify allowed origins, methods, headers, and credentials.
    • Python (Flask/Django): Flask has `Flask-CORS`, and Django offers `django-cors-headers` for easy configuration.
    • Java (Spring Boot): Spring Framework provides `@CrossOrigin` annotations on controllers or global CORS configuration for fine-grained control.
    • .NET (ASP.NET Core): Has a robust CORS policy system that can be configured in `Startup.cs` or using attributes.
  • Using an API Gateway Security: For API management solutions, CORS can often be configured directly at the gateway level (e.g., AWS API Gateway, Azure API Management, Kong). This centralizes the policy, so you don't need to implement it in every microservice.

Key Configuration Considerations:

  • Restrict `Access-Control-Allow-Origin`: Avoid using `*` (wildcard) in production environments unless your API is truly public and contains no sensitive user data. Instead, list specific origins (`https://yourfrontend.com, https://yourpartner.com`) or dynamically check the `Origin` header against a whitelist.
  • Allow Necessary Methods/Headers: Only allow the HTTP methods (`GET`, `POST`, `PUT`, `DELETE`, etc.) and custom headers that your API genuinely uses.
  • `Access-Control-Allow-Credentials`: If your API relies on cookies or HTTP authentication for cross-origin requests, enable this. Remember, if `Access-Control-Allow-Credentials` is true, then `Access-Control-Allow-Origin` cannot be `*`.
  • Preflight Cache (`Access-Control-Max-Age`): Set an appropriate duration for caching preflight responses to reduce overhead, but not so long that changes to CORS policies aren't picked up quickly.

Proper configuration ensures your web API interacts as intended while maintaining a secure posture against unauthorized access.

Common CORS Issues and Troubleshooting Tips

CORS errors can be notoriously cryptic, but understanding common pitfalls helps in effective troubleshooting. Most issues stem from misconfiguration on the server side.

  • `No 'Access-Control-Allow-Origin' header is present on the requested resource.` This is the most common error. It means your server isn't sending the `Access-Control-Allow-Origin` header at all, or its value doesn't match the client's `Origin` header.
    • Solution: Ensure your API server is correctly adding `Access-Control-Allow-Origin` to its responses, and that the value matches the domain from which your frontend is making requests.
  • Preflight Request Fails (e.g., 401, 403, 405 errors for `OPTIONS` request). The browser sends an `OPTIONS` request, but the server responds with an error status code before the actual request can even be made.
    • Solution: Ensure your server is configured to handle `OPTIONS` requests and respond with 200 OK, including the appropriate `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers`. Sometimes, authentication/authorization middleware might block `OPTIONS` requests before CORS headers are applied. Make sure CORS middleware runs early in the request pipeline.
  • Credentials (cookies, `Authorization` header) are not sent or received. Even with `Access-Control-Allow-Origin` set, cookies or `Authorization` headers might not be sent.
    • Solution: Both the client and server must explicitly enable credentials. On the client (JavaScript `fetch` or `XMLHttpRequest`), set `credentials: 'include'`. On the server, set `Access-Control-Allow-Credentials: true`. Remember, if credentials are allowed, `Access-Control-Allow-Origin` cannot be `*`.
  • Missing or Incorrect `Access-Control-Allow-Headers` or `Access-Control-Allow-Methods`. If your request uses custom headers or non-simple HTTP methods, but the preflight response doesn't explicitly allow them, the actual request will be blocked.
    • Solution: Verify that `Access-Control-Allow-Headers` includes all custom headers used by your client (e.g., `Authorization`, `X-Custom-Header`), and `Access-Control-Allow-Methods` includes all methods (e.g., `PUT`, `DELETE`).

Tools like browser developer consoles are your best friend here, as they clearly log CORS errors and show which headers are missing or incorrect. Using an API monitoring tool can also help inspect actual requests and responses at the server level.

CORS and Security: Best Practices

While CORS enables cross-origin communication, it's primarily a security mechanism. Misconfiguring it can open your API to vulnerabilities. Adhering to these best practices ensures both functionality and robust security:

  1. Restrict `Access-Control-Allow-Origin` Strictly: The most crucial rule. Never use `Access-Control-Allow-Origin: *` for APIs handling sensitive data or authenticated sessions. Always specify exact origins (`https://yourdomain.com`). If you need multiple origins, return only the `Origin` header value if it's on an allowed whitelist.
  2. Be Cautious with `Access-Control-Allow-Credentials`: Setting this to `true` allows browsers to send cookies and HTTP authentication credentials cross-origin. This is powerful but also risky. Only enable it when absolutely necessary, and always pair it with a specific (non-wildcard) `Access-Control-Allow-Origin`. This is especially relevant for handling API keys or tokens.
  3. Limit `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers`: Grant only the minimal set of HTTP methods and custom headers required by your legitimate clients. Avoid broad permissions like allowing all methods or headers (`*`).
  4. Place CORS Configuration Early in the Request Pipeline: Ensure your CORS middleware or configuration runs before any authentication, authorization, or other security checks. If a preflight `OPTIONS` request is blocked by authentication before CORS headers are applied, clients will encounter errors.
  5. Sanitize and Validate All Input: Even with perfect CORS, your API is vulnerable to other attacks. Implement rigorous input validation and sanitization for all data received, to protect against SQL injection, XSS, and other common vulnerabilities. This aligns with broader OWASP Top 10 recommendations.
  6. Consider API Rate Limiting: While not directly a CORS setting, rate limiting is crucial for protecting your API from abuse, brute-force attacks, and denial-of-service attempts, regardless of origin.

By thoughtfully applying these security practices, you can leverage CORS to enable necessary cross-origin communication without compromising the integrity of your web API.

While CORS is the standard solution for cross-origin communication in modern web APIs, it's not the only approach, and sometimes alternatives or complementary strategies are useful:

  • JSONP (JSON with Padding): An older technique that bypasses SOP by dynamically injecting <script> tags, leveraging the fact that script tags are not subject to SOP. While effective, JSONP is limited to GET requests, less secure, and generally considered obsolete compared to CORS. It is rarely recommended for new development.
  • Proxy Servers: A common workaround for CORS issues, especially in development. The client (frontend) makes a same-origin request to its own backend server (the proxy), which then forwards the request to the actual cross-origin API. Since the server-to-server request is not subject to browser SOP, the response is received by the proxy and then forwarded back to the client. This can add complexity but is sometimes used for specific scenarios or to centralize API orchestration.
  • Server-to-Server Communication: When resources are accessed directly between backend servers, not via a browser, CORS is irrelevant because SOP is a browser security feature. This is often the most secure way to handle sensitive cross-origin data exchanges.

For most modern web API interactions involving browsers, CORS remains the preferred, standardized, and most secure approach. Understanding these alternatives provides context on the broader landscape of cross-origin data handling.

Conclusion

CORS, or Cross-Origin Resource Sharing, is an indispensable part of secure and functional web API development. It serves as a necessary bridge, allowing web applications to access resources from different domains while adhering to strict browser security protocols. By understanding the Same-Origin Policy and the mechanisms of simple and preflight requests, developers can confidently configure their web APIs to work seamlessly across diverse environments.

Proper CORS implementation is not just about avoiding error messages. It is about building robust, secure, and interoperable web experiences that empower both developers and end users. Embracing these principles is fundamental for any modern API developer portal or platform.

FAQs

1. What is the main purpose of CORS in web API development?

The main purpose of CORS is to enable secure cross-origin resource sharing between a web application, the client, and a web API, the server, that are hosted on different origins. It provides a standardized way for the server to explicitly tell the browser which external origins are permitted to access its resources, overriding the browser's default Same-Origin Policy which would otherwise block such requests for security reasons.

2. How does the Same-Origin Policy relate to CORS?

The Same-Origin Policy, SOP, is a fundamental browser security mechanism that, by default, restricts web pages from making requests to a different origin than the one that served the page. CORS, Cross-Origin Resource Sharing, is an extension to HTTP that provides a controlled way to relax the SOP, allowing legitimate cross-origin requests to proceed when the server explicitly grants permission via specific HTTP headers.

3. What is the difference between a simple request and a preflight request in CORS?

A simple request is a basic cross-origin request such as GET, HEAD, or POST with specific Content-Type values and no custom headers. The browser sends it directly, and the server response must include Access-Control-Allow-Origin. A preflight request is an OPTIONS HTTP request sent automatically by the browser before a complex cross-origin request such as PUT, DELETE, requests with custom headers, or application/json content type. The preflight request asks the server for permission. Only if approved is the actual request sent.

4. Why is Access-Control-Allow-Origin:  generally discouraged for secure APIs?

Using Access-Control-Allow-Origin:  allows any website from any domain to make cross-origin requests to your API. While convenient for public APIs without sensitive data, it is generally discouraged for secure APIs that handle user data or authenticated sessions. A wildcard can expose your API to CSRF attacks or make it harder to prevent unauthorized data access if not combined with other robust security measures such as proper API testing.

5. Where should CORS be configured in a typical web API setup?

CORS is typically configured on the server side where your web API is hosted. This can be done within your application framework such as Express.js, Spring Boot, or ASP.NET Core using dedicated middleware or libraries. Alternatively, for complex deployments or microservices, CORS can be centrally configured at the API gateway level such as AWS API Gateway or an Nginx reverse proxy, which applies the policy before requests reach individual backend services.

Liked the post? Share on:

Don’t let your APIs rack up operational costs. Optimise your estate with DigitalAPI.

Book a Demo

You’ve spent years battling your API problem. Give us 60 minutes to show you the solution.

Get API lifecycle management, API monetisation, and API marketplace infrastructure on one powerful AI-driven platform.