Skip to main content

CORS — browser ↔ API

How cross-origin requests work for dwd-api-fastapi behind Nginx on msi-laptop-3.

Standard answer: configure CORS in the Python app

LayerHandle CORS?When
FastAPI (CORSMiddleware)Yes — primaryBrowser calls API on a different origin (port, host, or scheme)
NginxRarelyOnly if the backend cannot set headers (legacy app, static files)

This API already uses app-level CORS in config.py / main.py:

app.add_middleware(CORSMiddleware, **CORS_CONFIG)

Nginx should pass through Access-Control-* headers from the upstream — do not duplicate CORS in Nginx unless you remove it from the app (avoid double headers).

Why not Nginx-only?

  • Preflight OPTIONS must return correct Access-Control-Allow-Methods, Allow-Headers, and often Access-Control-Allow-Credentials
  • With credentials, Allow-Origin must be the specific requesting origin, not *
  • Per-environment origin lists are easier in code or env vars than maintaining nginx map blocks

Allowed origins today

From repos/dwd-api-fastapi/config.py:

OriginPurpose
http://localhost:3000Local React dev (Vite/CRA)
http://localhost:3001, 3002Alt dev ports
http://127.0.0.1:3000, …Same, 127.0.0.1
https://dwd.hydrotrek.com, …Production SPAs
Various 192.168.50.xPrior LAN hosts

Your current dev setup: browser origin http://localhost:3000 → API http://192.168.0.85localhost:3000 is already in the list. CORS is configured correctly for that pair.

When you deploy a SPA to a new hostname, add that exact origin (scheme + host + port) to CORS_ORIGINS in config.py, rebuild/redeploy the container, and redeploy.

Production example to add later:

"https://dwd-api.yourdomain.com",  # if SPA hosted here
"https://app.yourdomain.com",

Your error: CORS message + 500 — two layers

Browser console:

No 'Access-Control-Allow-Origin' header ...
GET http://192.168.0.85/graph-data?...base_url=https://localhost:7033/... 500

1. Primary issue: 500 Internal Server Error

The API on the server calls the ASP.NET backend using base_url from the query string:

base_url=https://localhost:7033/api/

Inside Docker on 192.168.0.85, localhost is the container, not your Windows PC. The fetch to ASP.NET fails → 500.

Fix: pass a URL the server can reach, e.g. your workstation’s LAN IP:

base_url=https://192.168.0.XX:7033/api/

Requirements:

  • ASP.NET listens on 0.0.0.0:7033 (or LAN IP), not only 127.0.0.1
  • Windows firewall allows inbound 7033 from the server
  • HTTPS cert trust if using https:// (API may disable verify for localhost — check create_httpx_client in main.py)

Or set DWD_API_BASE_URL in /etc/dwd-api-fastapi/dwd-api-fastapi.env to that reachable URL and omit base_url from the frontend.

2. CORS message is often misleading on failed responses

Browsers report CORS when the response lacks Access-Control-Allow-Origin. That happens when:

  • Nginx returns its own error page (502/504)
  • The app crashes before middleware adds headers (uncommon with FastAPI CORS on route errors)
  • Nginx returns 301/302 on OPTIONS preflight (e.g. trailing-slash redirect on /gqc/api/copilotkit/gqc/api/copilotkit/). Browsers fail with: "Redirect is not allowed for a preflight request". Fix: proxy the exact path without redirecting; see 15-api-reverse-proxy-base-paths-todo.md.

Fix the 500 first. Test from the server:

curl -sS -D- "http://127.0.0.1:8001/graph-data?project_id=...&scenario_id=..." \
-H "Origin: http://localhost:3000" | head -30

Look for HTTP/1.1 200 and access-control-allow-origin: http://localhost:3000.


Verify CORS (after API returns 200)

From Windows (replace query params with valid test values):

curl -v "http://192.168.0.85/health" -H "Origin: http://localhost:3000"

Expect:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true

Preflight (if frontend sends non-simple headers):

curl -v -X OPTIONS "http://192.168.0.85/graph-data" `
-H "Origin: http://localhost:3000" `
-H "Access-Control-Request-Method: GET"

Expect 204 or 200 with Access-Control-Allow-Methods.


Nginx (reference only — not needed for this API)

If you ever had to do CORS in Nginx without app support:

location / {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "*";
return 204;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
proxy_pass http://backend;
}

Use $http_origin with a map whitelist in production — do not reflect arbitrary origins. Prefer app-level CORS for FastAPI.


Checklist — new frontend origin

  • Add exact origin string to CORS_ORIGINS in config.py
  • Rebuild/restart API container
  • Confirm curl -H "Origin: ..." http://192.168.0.85/health shows Access-Control-Allow-Origin
  • Ensure base_url / DWD_API_BASE_URL is reachable from the server, not localhost on your PC