Security Tools

How to configure CORS headers and fix common errors

Learn how to set up Cross-Origin Resource Sharing headers correctly. Covers preflight requests, allowed origins, credentials, security risks, and debugging CORS errors.

SiteSecurityScore Team·11 min read·Updated Feb 17, 2026

What is CORS and the same-origin policy#

Every modern browser enforces the same-origin policy, a foundational security mechanism that prevents JavaScript running on one origin from reading responses that belong to a different origin. An origin is defined by the combination of scheme, hostname, and port. If any of those three components differ between two URLs, the browser treats them as separate origins and blocks the cross-origin request by default. This policy exists to prevent malicious websites from silently reading sensitive data, such as authenticated API responses or session tokens, from other sites the user is logged into.

Cross-Origin Resource Sharing (CORS) is the standard mechanism that relaxes the same-origin policy in a controlled way. When a server includes specific CORS response headers, it tells the browser that requests from designated external origins are allowed. Without CORS headers, any fetch or XMLHttpRequest to a different origin will fail, even if the server processes the request successfully on its back end. The browser simply discards the response before your JavaScript can access it.

CORS becomes essential any time your front-end application needs to communicate with a service on a different origin. This happens more often than many developers expect, especially in modern architectures where the API, CDN, and client application each run on their own domain or subdomain. Getting the configuration right is important because overly permissive CORS headers can expose your users to cross-origin attacks, while missing headers will break legitimate functionality. You can scan your website to check whether your current headers are configured securely.

Here are the most common scenarios that require CORS configuration:

  • A React or Vue front end hosted on app.example.com calling an API at api.example.com
  • Loading web fonts, scripts, or stylesheets from a third-party CDN
  • Microservices communicating across different subdomains or ports during development
  • Third-party integrations where external clients consume your public API

Understanding CORS headers and preflight requests#

CORS is controlled entirely through HTTP response headers that the server sends back to the browser. Each header serves a specific purpose, and misconfiguring even one of them can either break your application or create a security vulnerability. The six core CORS headers work together to define exactly which cross-origin requests your server will accept, what methods and headers are permitted, and how long the browser should cache the preflight result.

The six core CORS headers

  • Access-Control-Allow-Origin — Specifies which origin(s) are allowed to read the response. Can be a single origin or * for any origin.
  • Access-Control-Allow-Methods — Lists the HTTP methods (GET, POST, PUT, DELETE, etc.) the server permits for cross-origin requests.
  • Access-Control-Allow-Headers — Declares which request headers the client is allowed to send beyond the default safe-listed headers.
  • Access-Control-Allow-Credentials — When set to true, allows the browser to include cookies and authorization headers in cross-origin requests.
  • Access-Control-Max-Age — Tells the browser how many seconds it can cache the preflight response, reducing redundant OPTIONS requests.
  • Access-Control-Expose-Headers — Lists which response headers the client-side JavaScript can access beyond the default safe-listed set.

Before certain cross-origin requests are sent, the browser automatically issues a preflight request using the HTTP OPTIONS method. This preflight asks the server whether the actual request is allowed. Preflight is triggered whenever the request uses a method other than GET, HEAD, or POST, when it includes custom headers like Authorization or Content-Type: application/json, or when the request includes credentials. The server must respond to the OPTIONS request with the appropriate CORS headers and a 200 or 204 status code, or the browser will block the actual request entirely.

Understanding preflight is critical because many CORS errors occur not on the main request but on the preflight. If your server does not handle OPTIONS requests properly, or if your reverse proxy strips the CORS headers from the preflight response, the browser will never send the real request. For a related cross-origin mechanism that controls how resources are embedded by other sites, see our Cross-Origin Resource Policy guide.

How to use the CORS generator step by step#

Our CORS generator walks you through each decision so you can build a secure, production-ready configuration without memorizing header syntax. Start by opening the tool and choosing your origin mode. If your API is public and does not require credentials, the wildcard (*) option allows requests from any origin. For most applications, however, you should select the specific origins mode and enter the exact domains that need access, such as https://app.example.com.

Next, select the HTTP methods your API supports. Most REST APIs need GET, POST, PUT, and DELETE. If your API only serves read-only data, restrict this to GET and HEAD. After methods, add any custom request headers your front end sends, such as Authorization, Content-Type, or X-Request-ID. Then decide whether your requests include credentials (cookies or auth headers). If so, toggle the credentials option on, but remember that this is incompatible with a wildcard origin. Finally, set the max-age to control how long browsers cache the preflight response. A value of 86400 (24 hours) is common for production.

Once you have made your selections, the generator produces the complete header configuration. You can copy the result directly into your server configuration or middleware. Open the CORS generator to try it now. For a deeper dive into CORS mechanics, including edge cases around opaque responses and service workers, see MDN's comprehensive CORS guide.

Here is an example of what the generator produces for a typical single-page application calling a REST API:

Generated CORS Headers
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-ID
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Access-Control-Expose-Headers: X-RateLimit-Remaining, X-RateLimit-Reset

CORS configurations for common architectures#

Every architecture has different CORS requirements. Below are three common patterns with ready-to-use header configurations. You can adapt these examples using our security tools to match your specific setup.

API backend serving a single frontend

This is the most common pattern. Your React, Angular, or Vue application is hosted on one domain and your API lives on another. Because the front end sends authentication cookies or bearer tokens, you need credentials support with a specific origin. Never use a wildcard here.

Nginx: single-frontend API
# Nginx configuration for single-frontend API
location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Max-Age' '86400';
        return 204;
    }
    add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Expose-Headers' 'X-Request-Id' always;
}

Public API with open access

If you are building a public data API that does not require authentication, such as a weather service or open data endpoint, a wildcard origin is appropriate. Because credentials are not involved, this configuration is safe and keeps integration friction low for consumers.

Express.js: public API
# Express.js middleware for public API
app.use('/api/public', (req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.setHeader('Access-Control-Max-Age', '3600');
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }
    next();
});

Microservices with multiple origins

When several front-end applications or partner sites need access to your API, you cannot use a wildcard if credentials are required. Instead, validate the incoming Origin header against a whitelist and reflect it back dynamically. This approach gives you per-origin control without sacrificing security.

Node.js: dynamic origin whitelist
# Node.js dynamic origin whitelist
const allowedOrigins = [
    'https://app.example.com',
    'https://admin.example.com',
    'https://partner.otherdomain.com'
];

app.use((req, res, next) => {
    const origin = req.headers.origin;
    if (allowedOrigins.includes(origin)) {
        res.setHeader('Access-Control-Allow-Origin', origin);
        res.setHeader('Access-Control-Allow-Credentials', 'true');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
        res.setHeader('Access-Control-Max-Age', '86400');
        res.setHeader('Vary', 'Origin');
    }
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }
    next();
});

Security risks of misconfigured CORS#

Never combine wildcard origin with credentials

Setting Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: true is explicitly forbidden by the CORS specification. Browsers will reject the response entirely. However, some server frameworks silently work around this by reflecting the request's Origin header back, which is equally dangerous because it trusts every origin on the internet with the user's credentials.

One of the most dangerous CORS misconfigurations is blindly reflecting the Origin request header into the Access-Control-Allow-Origin response header without validating it against a whitelist. This effectively allows any website to make authenticated requests to your API on behalf of logged-in users. An attacker can host a malicious page that silently reads the victim's private data, changes account settings, or initiates transactions, all without the user's knowledge.

Credential exposure amplifies the risk. When Access-Control-Allow-Credentials is set to true, the browser sends cookies and authorization headers with every cross-origin request. If the origin is not properly restricted, attackers gain full access to the victim's session. This makes the combination of origin reflection and credentials the most critical CORS vulnerability, often equivalent to a full account takeover.

Watch out for these common CORS security anti-patterns:

  • Reflecting any Origin header without validation, which lets every website on the internet access your API as the user
  • Using regex patterns that are too loose, such as matching .example.com which also matches evil-example.com
  • Allowing the null origin, which can be triggered from sandboxed iframes and data URIs controlled by an attacker
  • Forgetting to include the Vary: Origin header when dynamically reflecting origins, causing cache-poisoning issues at the CDN layer
  • Exposing sensitive headers like Set-Cookie or internal trace IDs through Access-Control-Expose-Headers without considering the security implications

With overly permissive CORS, an attacker can craft a page that reads the victim's profile data, exfiltrates API keys stored in local storage, or performs state-changing actions like transferring funds or modifying account details. The attack is entirely invisible to the user because it runs in the background of an otherwise normal-looking page. This is why it is critical to treat CORS configuration as a security boundary, not just a convenience toggle.

Debugging CORS errors and testing your configuration#

CORS errors are some of the most common issues developers encounter when building modern web applications, and the browser's error messages can be confusing at first. The most frequent error is No 'Access-Control-Allow-Origin' header is present on the requested resource, which means the server did not include the required CORS header in its response. Another common message, The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include', indicates that you are trying to send credentials to a wildcard-origin server. A third message, Method PUT is not allowed by Access-Control-Allow-Methods, means the server's method whitelist does not include the HTTP method your code is using.

To inspect CORS behavior, open your browser's DevTools and navigate to the Network tab. Look for the preflight OPTIONS request that precedes your actual request. Click on it and check the response headers for the Access-Control-Allow-* headers. If the OPTIONS request returns a non-2xx status or is missing CORS headers, that is where the problem lies. Also check the Console tab, where the browser logs detailed CORS error messages that usually tell you exactly which header value is wrong or missing.

Follow these steps to systematically debug a CORS issue:

  • Open DevTools Network tab, filter by the failing request URL, and check whether an OPTIONS preflight was sent
  • Inspect the preflight response status code and headers; confirm Access-Control-Allow-Origin matches your front-end origin exactly
  • Verify that every method your client sends is listed in Access-Control-Allow-Methods
  • Verify that every custom header your client sends is listed in Access-Control-Allow-Headers
  • If using credentials, confirm the origin is not a wildcard and Access-Control-Allow-Credentials: true is present
  • Check for reverse proxies, load balancers, or CDNs that may be stripping or overwriting your CORS headers

You can also test your CORS headers from the command line using curl. This is useful for verifying the server-side configuration independently of the browser:

curl: preflight test
# Send a preflight OPTIONS request and inspect CORS headers
curl -X OPTIONS https://api.example.com/data \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  -v 2>&1 | grep -i "access-control"

# Expected output:
# < Access-Control-Allow-Origin: https://app.example.com
# < Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# < Access-Control-Allow-Headers: Authorization, Content-Type
# < Access-Control-Allow-Credentials: true
# < Access-Control-Max-Age: 86400
Was this helpful?
Share

Ready to configure your CORS headers?

Build safe cross-origin configurations in minutes with the CORS header generator.