Skip to main content

CORS

High

Cross-Origin Resource Sharing

CORS controls which origins can read responses from your API. The browser enforces CORS – servers declare policy via response headers. A wildcard Access-Control-Allow-Origin: * combined with credentials is a critical misconfiguration that exposes the API to any malicious website.

Overview

Browsers implement the Same-Origin Policy (SOP): a script on https://app.example.com cannot read responses from https://api.other.com by default. CORS is a mechanism that lets servers relax this policy by declaring trusted origins in response headers.

CORS works via a preflight: before sending a cross-origin request with a non-simple method or custom headers, the browser sends an OPTIONS request. The server responds with which origins, methods, and headers are permitted. The browser only sends the actual request if the preflight succeeds.

The most dangerous CORS misconfiguration is reflecting the request Origin header back as Access-Control-Allow-Origin while also setting Access-Control-Allow-Credentials: true. This lets any website make authenticated cross-origin requests to your API using the visitor's cookies.

The Attack: CORS Credential Theft via Origin Reflection

A misconfigured server reflects the attacker's origin and allows credentials. Any website can silently make authenticated API calls on behalf of logged-in users visiting that site.

Vulnerable server response
http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"user": "alice", "email": "[email protected]", "token": "secret"}

Defenses

1Allowlist specific origins

Maintain an explicit list of trusted origins. Never reflect the request Origin header back without checking it against the allowlist first. Reject requests from unlisted origins.

Correct CORS for authenticated API
http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Content-Type: application/json

2Never use wildcard with credentials

Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true cannot be used together – browsers reject this combination. If credentials are needed, specify the exact origin.

Public API without credentials – wildcard is fine
http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
# No credentials – wildcard is safe for public read-only APIs

3Always set Vary: Origin

When you return different CORS headers based on the request Origin, add Vary: Origin to the response. This prevents CDNs and shared caches from serving a response with one origin's CORS headers to a different origin.

Vary header prevents cache poisoning
http
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin

Checklist

  • Allowlist only specific, verified origins – never reflect Origin blindly
  • Never combine Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true
  • Set Vary: Origin when the CORS response depends on the request origin
  • Handle OPTIONS preflight requests with the correct headers and a 204 response
  • Do not include Access-Control-Allow-Origin in responses to same-origin requests
  • Review CORS configuration after every new subdomain or API surface addition

Related Headers

Related Status Codes

FAQ

Does CORS protect the server?

No. CORS is enforced by the browser, not the server. A server-side tool like curl or a non-browser client ignores CORS entirely. CORS only restricts which browser scripts can read cross-origin responses. For server-to-server requests, use authentication instead.

Why do I get CORS errors even though my server returns the right headers?

Check for three things: (1) the OPTIONS preflight may be missing the right headers – ensure your server handles OPTIONS requests explicitly, (2) the Vary: Origin header may be missing causing a CDN to cache the wrong CORS response, (3) the origin in your request may not exactly match the allowlist (protocol, port, subdomain all matter).

Can I use Access-Control-Allow-Origin: * for my API?

Yes, if your API is public and does not use cookies or Authorization headers for authentication. The wildcard is safe for read-only public APIs. Never use it for authenticated APIs.