Skip to content
← All articles

CORS: Complete Guide to Access-Control-Allow

CORS: The Complete Guide to Access-Control-Allow

CORS (Cross-Origin Resource Sharing) is how a server opts the browser in to using its resources from a different origin. Without CORS, any JS on another domain cannot read your API документацию's response — that is the foundation of the Same-Origin Policy. This article walks through preflight, the Access-Control-Allow-* headers, credentials, and the infamous "has been blocked by CORS policy" errors.

Same-Origin Policy and the need for CORS

An origin is a (scheme, host, port) tuple. https://app.example.com and https://api.example.com are different origins. By default the browser refuses to let JavaScript read cross-origin fetch responses. CORS is the official way for the server to say "yes, I'm fine with this." The protocol is defined in the WHATWG Fetch Standard.

Simple requests

A request is "simple" when the method is GET/POST/HEAD, there are no custom headers, and Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain. The server responds:

Access-Control-Allow-Origin: https://app.example.com
Vary: Origin

Vary: Origin is critical when the allowed origin is dynamic — otherwise the CDN will cache the response for the first origin and serve it to everyone else.

Preflight OPTIONS

Complex requests (PUT/DELETE/PATCH, custom headers, JSON body) are preceded by an OPTIONS request. The server must answer with the permission headers:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
Access-Control-Max-Age: 86400

Max-Age caches the preflight in the browser, saving 50+ ms per request.

Credentials (cookies, Authorization)

To send cookies or an Authorization header the client calls fetch(..., { credentials: 'include' }) and the server answers:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Crucial: with Allow-Credentials: true you cannot use Access-Control-Allow-Origin: * — only an explicit origin. This is a data-exfil guardrail.

nginx

map $http_origin $cors_origin {
  default "";
  "https://app.example.com" $http_origin;
  "https://admin.example.com" $http_origin;
}

location /api/ {
  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Access-Control-Max-Age "86400" always;
    add_header Vary "Origin" always;
    return 204;
  }
  add_header Access-Control-Allow-Origin $cors_origin always;
  add_header Access-Control-Allow-Credentials "true" always;
  add_header Vary "Origin" always;
  proxy_pass http://backend;
}

Express and NestJS

// Express
import cors from 'cors';
app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  credentials: true,
  methods: ['GET','POST','PUT','DELETE','PATCH'],
  maxAge: 86400,
}));

// NestJS
app.enableCors({
  origin: (origin, cb) => {
    const allow = ['https://app.example.com'];
    if (!origin || allow.includes(origin)) cb(null, true);
    else cb(new Error('CORS blocked'));
  },
  credentials: true,
});

Common mistakes

"No Access-Control-Allow-Origin header" — the server never set it. Verify with curl -I -H "Origin: https://app.example.com" https://api.example.com/.

"Wildcard with credentials" — either drop credentials or replace * with an explicit origin.

Cache serves the wrong Allow-Origin — add Vary: Origin.

OPTIONS returns 401 — preflight travels without auth tokens; whitelist OPTIONS in your auth middleware.

Debugging

Browser DevTools → Network → inspect the preflight. In the terminal:

curl -i -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type,Authorization" \
  https://api.example.com/endpoint

The enterno.io CORS Checker simulates any Origin and shows the server response without touching frontend code.

FAQ

Is CORS a server protection? No. CORS is a browser restriction that the server can relax. curl and any server-side HTTP client ignore it.

Can I use Access-Control-Allow-Origin: *? Only for public APIs without credentials — CDN assets, open data.

What breaks preflight most often? A missing Access-Control-Allow-Headers for Authorization or X-API-Key.

Does same-origin need CORS? No — same-origin traffic bypasses CORS completely.

Conclusion

CORS is a declaration, not an antivirus. Keep an explicit allowlist, always ship Vary: Origin, never combine * with credentials, monitor header changes with enterno monitors, and use the CORS Checker. See also: HTTP Security Headers, API rate limiting.

Check your website right now

Check now →
More articles: SEC
SEC
Prevent XSS Attacks: Escaping, CSP and Trusted Types
15.04.2026 · 4 views
SEC
API Rate Limiting: Token Bucket, 429, Retry-After
15.04.2026 · 4 views
SEC
HTTP to HTTPS Migration: Redirects, Mixed Content, HSTS
15.04.2026 · 5 views
SEC
Cookie Security: HttpOnly, Secure, SameSite, __Host-
15.04.2026 · 7 views