TroubleshootingIntermediate

Fix CORS Errors: Complete Debugging Guide

CORS errors block legitimate requests and frustrate users. Identify CORS configuration issues, understand browser behavior, and implement correct cross-origin policies.

13 min read
Atatus Team
Updated March 15, 2025
6 sections
01

Understanding How CORS Works

CORS is a browser security mechanism—understanding it precisely is the prerequisite to fixing it.

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which cross-origin requests are allowed. A cross-origin request is any request where the origin of the page making the request (scheme + host + port) differs from the origin of the resource being requested. When JavaScript code at https://app.example.com fetches https://api.example.com/data, this is a cross-origin request because the hostnames differ, even though both share the example.com domain.

CORS is enforced by the browser, not the server. The server receives the cross-origin request, processes it normally, and includes (or omits) CORS headers in its response. The browser then checks those headers and either delivers the response to the JavaScript code or blocks it with a CORS error. This means the server processes all requests—it has no way to prevent cross-origin requests at the network level—but the response is withheld from client-side code when CORS headers are missing or incorrect. This is an important point: CORS does not protect server data from server-side scripts or tools like curl, only from browser-based JavaScript.

Simple requests and preflight requests are the two categories of cross-origin requests with different CORS handling. Simple requests (GET/HEAD/POST with certain content types and no custom headers) are sent directly to the server, which must include CORS headers in its response. Complex requests (any PUT/PATCH/DELETE, any request with custom headers, any POST with a non-form content type like application/json) trigger a preflight OPTIONS request before the actual request. The preflight checks whether the server accepts the actual request's method and headers, and the browser proceeds with the actual request only if the preflight succeeds.

The browser's same-origin policy and CORS restrictions apply to all APIs including the Fetch API, XMLHttpRequest, and older APIs. Images loaded with the img tag, scripts loaded with script tags, and stylesheets loaded with link tags bypass CORS restrictions by default. CORS restrictions apply specifically to JavaScript that reads response data from cross-origin requests. Understanding this distinction explains why you can include a CDN-hosted script without CORS issues but cannot use the Fetch API to read its source text from JavaScript.

02

Identify All CORS-Related Failures

Systematic error identification reveals the full scope of CORS problems across your application.

CORS errors in browser developer tools appear with the message 'has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource' or similar variations. The Network tab shows the actual request and response, including the presence or absence of CORS headers. The Console tab shows the CORS error message with the specific header that was missing or incorrect. Capture both the request headers (including the Origin header the browser sent) and the response headers (or lack thereof) to diagnose the exact configuration issue.

Track CORS errors across all endpoints in your application using error monitoring and RUM tools. Individual CORS errors in browser developer tools are useful for development debugging, but production CORS errors affecting users require systematic monitoring. Browser error monitoring tools capture CORS failures alongside JavaScript errors, allowing you to see which origins are being blocked, which endpoints are generating CORS errors, and how many users are affected. Alert on new CORS error patterns that appear after deployments, as they often indicate configuration regressions.

Preflight OPTIONS request failures are distinct from main request CORS failures and require different diagnosis. A preflight failure means the server either did not respond to the OPTIONS request at all, returned a non-2xx status code, or returned CORS headers that do not authorize the actual request's method, headers, or origin. In the Network tab, look for OPTIONS requests to the same URL as your failing request—these are preflights. A missing OPTIONS handler on your API server is a common cause of preflight failures for APIs that receive CORS requests.

Distinguish between CORS errors from different causes to apply the right fix. A missing Access-Control-Allow-Origin header means the server has no CORS configuration at all. A response with Access-Control-Allow-Origin: * that still fails means the request is sending credentials (cookies or Authorization headers) and wildcard origins are incompatible with credentials. A response with the wrong origin in Access-Control-Allow-Origin means the server is using a static allowed origin list and the current request origin is not in the list. Each cause has a specific remediation.

03

Analyze CORS Headers and Configuration

Understanding each CORS header's role enables precise configuration.

Access-Control-Allow-Origin is the primary CORS response header. It specifies which origins are allowed to read the response. The value can be a specific origin ('https://app.example.com'), the wildcard ('*' allowing any origin), or null (rarely useful). For APIs that serve multiple allowed origins, echo back the request's Origin header value in Access-Control-Allow-Origin after validating it against an allowlist—this allows dynamic CORS for multiple legitimate origins while blocking unauthorized ones. Never use wildcards for APIs that process authentication credentials, as browsers block wildcard responses to credentialed requests.

Access-Control-Allow-Methods specifies which HTTP methods are permitted for cross-origin requests. Preflight responses must include this header with the methods the server accepts. A preflight for a PUT request will fail if the response includes Access-Control-Allow-Methods: GET, POST but not PUT. List all the HTTP methods your API endpoints support: typically GET, POST, PUT, PATCH, DELETE, and OPTIONS. Some frameworks automatically handle OPTIONS for preflight; others require explicit route handlers.

Access-Control-Allow-Headers specifies which request headers the browser is permitted to include in cross-origin requests. Headers not in the simple headers list (Accept, Content-Type with basic values, Content-Language, Accept-Language) must be listed in Access-Control-Allow-Headers in preflight responses. The Authorization header for token-based authentication, Content-Type: application/json, and custom headers like X-API-Key must all be explicitly listed. A common mistake is omitting the Content-Type header from the allowed headers list while also requiring application/json request bodies.

Access-Control-Allow-Credentials is required when the cross-origin request includes cookies, HTTP authentication, or TLS client certificates. Setting this header to true allows the browser to include credentials in the request and allows client-side code to read the response. This header cannot be combined with Access-Control-Allow-Origin: *—when credentials are involved, the specific origin must be explicitly allowed. Failing to set Access-Control-Allow-Credentials: true means cookies set by the API domain will not be sent with cross-origin requests, breaking session-based authentication in cross-origin scenarios.

04

Implement Correct CORS Policies

Proper CORS implementation balances security with functionality.

Implement CORS in server-side middleware that applies to all routes rather than per-route configuration. This ensures all endpoints consistently return appropriate CORS headers and prevents omitting CORS configuration from new routes. In Express.js, use the cors package; in Django, use django-cors-headers; in Spring Boot, use @CrossOrigin or global CORS configuration with WebMvcConfigurer. Framework-level CORS middleware handles preflight OPTIONS requests automatically and ensures CORS headers are added to all responses including error responses, which are often missed in per-route configuration.

Configure environment-specific allowed origins using a list that can be updated without code changes. Hardcoding allowed origins in application code requires a deployment to update them; reading from environment configuration allows changes through configuration management. Maintain separate allowlists for development (localhost:3000, localhost:8080), staging (staging.example.com), and production (app.example.com, www.example.com). Validate the request Origin header against the appropriate allowlist for the environment and return the matching origin or an error for unrecognized origins.

Set the Access-Control-Max-Age header to reduce preflight request frequency. Preflight caching allows browsers to skip the OPTIONS request for subsequent requests to the same URL with the same method and headers, using the cached preflight response for the duration specified by Max-Age (in seconds). Setting Access-Control-Max-Age: 86400 (1 day) means browsers preflight each unique endpoint only once per day rather than on every request, significantly reducing the number of OPTIONS requests your server handles. Maximum values vary by browser: Chrome honors up to 7200 seconds, Firefox up to 86400.

Handle error responses with CORS headers. A common CORS pitfall is that authentication middleware, rate limiting middleware, or validation middleware returns error responses (401, 400, 429) before the CORS middleware runs, resulting in error responses without CORS headers. The browser then reports a CORS error rather than the actual HTTP error, confusing debugging. Ensure your CORS middleware runs before all other middleware and is configured to add CORS headers to all responses, including error responses. In Express.js, the cors() middleware should be the first middleware registered.

05

Debug CORS Issues in Development

Efficient development debugging saves time and prevents CORS issues from reaching production.

Browser developer tools provide complete CORS debugging information when used correctly. In Chrome DevTools Network tab, select a failed request and examine the Response Headers for missing or incorrect CORS headers. The Request Headers show the Origin header the browser sent, which must match the server's allowed origin. For preflight failures, find the preceding OPTIONS request with the same URL and examine its response headers. Enable 'Preserve log' in the Network tab to prevent log clearing on page navigation, which can hide the preflight request.

The Postman or curl output for a cross-origin request does not show CORS failures because CORS is enforced by browsers, not network clients. When you test an API with curl and it returns data successfully, it does not mean CORS is configured correctly—curl does not enforce CORS. Always test CORS behavior from a browser context with the correct origin, either through your actual application or through a simple HTML test page hosted at the correct origin. Browser extensions that disable CORS enforcement are useful for development but mask the actual CORS configuration state.

Reverse proxy configuration in development (Vite proxy, Create React App proxy, webpack-dev-server proxy) routes API requests through the development server on the same origin, bypassing CORS entirely. This is convenient but means CORS issues that exist in production are invisible during development. To test CORS configuration in development, either disable the proxy configuration and test directly against the API URL, or use a browser extension that logs CORS behavior without disabling the enforcement mechanism.

Monitor for CORS configuration drift between environments by running automated CORS validation tests as part of your deployment pipeline. A simple script that makes cross-origin requests from representative origins to all API endpoints and validates the CORS response headers can catch configuration regressions before they affect users. Include tests for credentialed requests, OPTIONS preflight responses, and error responses to ensure CORS headers are present in all scenarios. Run these tests against staging before deploying to production.

06

Secure CORS Configuration Against Misuse

CORS is a security mechanism—misconfiguration creates vulnerabilities, not just functionality issues.

Avoid overly permissive CORS configurations that create security vulnerabilities. The most dangerous misconfiguration is dynamically reflecting any Origin header value in Access-Control-Allow-Origin without validation—this allows any website on the internet to make authenticated cross-origin requests on behalf of your users. Validate origin values against an explicit allowlist of trusted origins rather than echoing any origin. A strict allowlist of known legitimate origins is more secure than any dynamic matching based on substring matching or regular expressions that may have unexpected edge cases.

Null origin handling requires careful thought. Some environments (local file:// pages, sandboxed iframes, certain mobile webviews) send Origin: null. Allowing null origins with credentials enabled means any page in a sandboxed iframe can make credentialed requests to your API. Most production APIs should not allow null origins. If you need to support specific scenarios involving null origins, implement additional validation beyond origin checking to prevent abuse.

CORS and CSRF (Cross-Site Request Forgery) protection are complementary but distinct mechanisms. CORS prevents unauthorized cross-origin JavaScript from reading responses; CSRF protection prevents forged state-changing requests. CORS alone does not protect against CSRF for simple requests (GET/POST with form content type) that are not subject to preflight. Implement CSRF tokens for state-changing operations even when CORS is configured, as CORS does not protect against all CSRF scenarios. Modern SameSite cookie attributes provide another layer of protection by restricting cookie sending in cross-site contexts.

Audit your CORS configuration periodically and remove stale allowed origins. Development origins (localhost), demo environments, and decommissioned partner domains that remain in your production CORS allowlist represent unnecessary attack surface. Maintain your CORS allowlist as code that is reviewed in pull requests when origins are added or removed, and set up automated monitoring that alerts on changes to the CORS configuration in production. Each allowed origin is a potential entry point for cross-origin attacks and should be maintained with the same rigor as other security configuration.

Key Takeaways

  • CORS is enforced by browsers, not servers—the server processes all requests; the browser decides whether to deliver the response to JavaScript based on CORS headers
  • Preflight OPTIONS requests occur before complex cross-origin requests; your API must handle OPTIONS and return appropriate Access-Control-Allow-Methods and Access-Control-Allow-Headers headers
  • Wildcard origin (Access-Control-Allow-Origin: *) cannot be used with credentialed requests (cookies, Authorization headers)—use specific allowed origins for authenticated APIs
  • CORS middleware must run before authentication and validation middleware to ensure error responses also include CORS headers—otherwise CORS errors mask the actual HTTP error
  • Never reflect any Origin header value without validation against an allowlist—dynamically echoing origins without validation allows any website to make authenticated requests on users' behalf
  • Access-Control-Max-Age caches preflight responses in the browser for up to 24 hours, reducing OPTIONS request volume significantly for APIs with stable endpoint configurations
Get started today

Monitor your applications with Atatus

Put the concepts from this guide into practice. Set up full-stack observability in minutes with no credit card required.

No credit card required14-day free trialSetup in minutes

Related guides