CORS: Cross-Origin Requests Explained

What CORS actually is (a browser relaxation, not server security), preflight, credentialed requests, and the config that finally makes it work.

BeginnerUpdated 2026-10-2950K views

CORS is not a server-side security feature

Same-origin policy is a BROWSER rule: scripts on site A may not read responses from site B. CORS is the browser's controlled way to relax that rule when the SERVER agrees. A non-browser client (curl, Postman, a server-side script) never sends CORS checks — the API is not protected by CORS.

StatusPreflight?Example
简单请求(GET/HEAD/POST + 少量头)否普通表单 POST、JSON GET
自定义头 / PUT/DELETE / Content-Type 为 JSON是POST application/json + Authorization
携带 Cookie(credentials)是且必须精确匹配 Origin,不能是 *
# 预检请求的样子(浏览器自动发的 OPTIONS)
OPTIONS /api/orders HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization,Content-Type

# 服务端同意了,回这套头:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Authorization,Content-Type
Access-Control-Max-Age: 86400        ← 预检结果缓存一天

The credentialed trap

The moment the request carries credentials (cookies, Authorization), the browser demands more: Access-Control-Allow-Origin must echo the EXACT origin (wildcard * is rejected), and you must send Access-Control-Allow-Credentials: true. This is the most common 'why did it work in Postman but not the browser' case.

# 后端 Nginx 的标准 CORS 配置(支持带凭证)
location /api/ {
    # 回显来源而不是 *(配合正则白名单)
    if ($http_origin ~* '^https://(www|admin)\.example\.com$') {
        add_header Access-Control-Allow-Origin  $http_origin always;
        add_header Access-Control-Allow-Credentials true always;
        add_header Vary Origin always;
    }
    add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always;
    add_header Access-Control-Allow-Headers 'Authorization,Content-Type' always;
    if ($request_method = OPTIONS) {
        add_header Content-Length 0;
        return 204;
    }
    proxy_pass http://api;
}
Pair Access-Control-Allow-Origin: <echo> with Vary: Origin, otherwise intermediate caches can hand site B's allowed-origin header to site A, breaking or wrongly allowing cross-origin reads.
  • Browsers do not apply CORS to <img> / <script> tags (they load, but scripts can't read the response) — that's why JSONP worked and why CSP matters
  • Special headers like Cookie, Authorization, X-Requested-With trigger preflight — expect the OPTIONS round-trip on every session
  • If the OPTIONS is handled by a framework filter, make sure it runs BEFORE auth (a 401 on preflight = dead API from the browser)

Friends & Partners