Back to Blog
Guide6 min read2026-05-01

CORS Explained: How to Fix Cross-Origin Errors in Web Apps

Demystify CORS errors, understand the Same-Origin Policy, and learn exactly how to configure correct CORS headers in your API.

CORS Explained: How to Fix Cross-Origin Errors in Web Apps

Few errors frustrate web developers more than this one:

Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

This guide explains what CORS is, why it exists, and exactly how to fix it.

The Same-Origin Policy

Browsers enforce the Same-Origin Policy (SOP) — a security mechanism that prevents JavaScript on one origin from reading responses from a different origin.

An origin is defined as: protocol + hostname + port.

URLSame origin as https://app.example.com?
https://app.example.com/path✅ Yes
http://app.example.com❌ No (different protocol)
https://api.example.com❌ No (different subdomain)
https://app.example.com:8080❌ No (different port)

SOP protects users: a malicious site can't use your logged-in session to make authenticated requests to your bank's API and read the response.

What CORS Is

CORS (Cross-Origin Resource Sharing) is a mechanism that lets servers opt in to allowing cross-origin requests. It works via HTTP headers that the server sends.

CORS doesn't block the request — the browser does. The server just tells the browser what's allowed.

Simple vs. Preflight Requests

Simple requests (GET, POST with plain text/form data) are sent directly, and the browser checks the response headers.

Preflighted requests (PUT, DELETE, POST with JSON, custom headers) trigger an automatic OPTIONS request first:

OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

The server must respond to this OPTIONS request with appropriate headers before the browser sends the real request.

The CORS Headers You Need

Server response headers:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
  • Allow-Origin — Which origins are allowed. Use * for public APIs (can't be combined with credentials).
  • Allow-Methods — Which HTTP methods are allowed.
  • Allow-Headers — Which request headers are allowed.
  • Allow-Credentials — Whether cookies and auth headers are allowed (requires specific origin, not *).
  • Max-Age — How long browsers cache the preflight response (seconds).

Fixing CORS in Common Backends

Node.js / Express

const cors = require('cors');

app.use(cors({
  origin: ['https://app.example.com', 'https://www.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

// Handle preflight for all routes
app.options('*', cors());

Python / FastAPI

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Nginx

location /api/ {
    add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Max-Age' 86400;
        return 204;
    }

    proxy_pass http://localhost:8080;
}

Common Mistakes

Using * with credentials: You cannot use Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true together. Browsers reject this. Specify the exact origin.

Not handling OPTIONS: If your server doesn't respond to OPTIONS preflight requests, cross-origin requests with custom headers will silently fail.

Adding CORS headers in the frontend: CORS headers must come from the server. You cannot fix CORS from the browser.

Multiple origins: Access-Control-Allow-Origin only accepts one value (or *). To support multiple origins, check the request's Origin header server-side and echo it back if it's in your allowlist.

const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  next();
});

Debugging CORS

  1. 1Open browser DevTools → Network tab
  2. 2Find the failing request
  3. 3Check the response headers — is Access-Control-Allow-Origin present?
  4. 4If it's an OPTIONS preflight, check if it returned 200/204
  5. 5Use curl to verify server headers independently:
curl -I -X OPTIONS https://api.example.com/data   -H "Origin: https://app.example.com"   -H "Access-Control-Request-Method: POST"

Conclusion

CORS errors are a browser security feature, not a bug. Fix them server-side by returning the correct Access-Control-Allow-* headers. When deploying APIs on PandaStack, your containerized backend handles CORS configuration — the platform provides networking and HTTPS termination. See [docs.pandastack.io](https://docs.pandastack.io) for deployment guides.

Ready to deploy?

Start free on PandaStack — no credit card required.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also