Skip to content
RU
← All articles

CORS Explained: Cross-Origin Resource Sharing Guide

Browser developer tools with red CORS errors in the console

In short. CORS is the browser asking your server whether another origin may read a response — it is not a server-side access control. Blocking happens in the browser, after the request was made, which is why a CORS error never means the request failed to arrive.

Cross-Origin Resource Sharing (CORS) is a security mechanism built into web browsers that controls how web pages from one origin (domain, protocol, port) can request resources from a different origin. Without CORS, browsers enforce the Same-Origin Policy (SOP), which blocks cross-origin HTTP requests made by JavaScript. CORS provides a safe, standardized way to relax this restriction when needed.

The Same-Origin Policy

The Same-Origin Policy is one of the fundamental security mechanisms of the web. Two URLs have the same origin if they share the same protocol, domain, and port:

URL AURL BSame Origin?Reason
https://example.com/page1https://example.com/page2YesSame protocol, domain, port
https://example.comhttp://example.comNoDifferent protocol
https://example.comhttps://api.example.comNoDifferent subdomain
https://example.comhttps://example.com:8080NoDifferent port
https://example.comhttps://other.comNoDifferent domain

Without the Same-Origin Policy, any website could read data from your banking app, email, or social media accounts using JavaScript — as long as you were logged in.

How CORS Works

CORS works through HTTP headers. When a browser makes a cross-origin request, it includes an Origin header. The server responds with Access-Control-* headers indicating whether the request is allowed:

# Browser sends:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

# Server responds:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json

{"data": "response"}

If the server does not include the appropriate CORS headers, the browser blocks the response and logs a CORS error in the console.

Simple Requests vs Preflight Requests

The browser categorizes cross-origin requests into two types:

Simple Requests

A request is "simple" if it meets all of these conditions:

  • Method is GET, HEAD, or POST
  • Only "safe" headers are used: Accept, Accept-Language, Content-Language, Content-Type
  • Content-Type is one of: application/x-www-form-urlencoded, multipart/form-data, text/plain

Simple requests are sent directly to the server. The browser checks the response headers after the fact.

Preflight Requests

Any request that does not qualify as "simple" triggers a preflight — an automatic OPTIONS request that the browser sends before the actual request:

# Step 1: Browser sends preflight
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

# Step 2: Server responds to preflight
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

# Step 3: Browser sends the actual request
PUT /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...

{"key": "value"}

CORS Headers Reference

HeaderDirectionPurpose
OriginRequestTells the server where the request comes from
Access-Control-Allow-OriginResponseSpecifies which origins can access the resource
Access-Control-Allow-MethodsResponseAllowed HTTP methods for preflight
Access-Control-Allow-HeadersResponseAllowed request headers for preflight
Access-Control-Allow-CredentialsResponseWhether cookies/auth headers are allowed
Access-Control-Expose-HeadersResponseResponse headers the browser can access via JS
Access-Control-Max-AgeResponseHow long to cache preflight results (seconds)
Access-Control-Request-MethodRequestMethod the actual request will use (preflight)
Access-Control-Request-HeadersRequestHeaders the actual request will use (preflight)

Common CORS Configurations

Allow a Specific Origin

# 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-Max-Age 86400 always;

    if ($request_method = OPTIONS) {
        return 204;
    }

    proxy_pass http://backend;
}

Allow Multiple Origins (Dynamic)

# Nginx — check Origin against allowed list
map $http_origin $cors_origin {
    default "";
    "https://app.example.com" $http_origin;
    "https://admin.example.com" $http_origin;
    "https://staging.example.com" $http_origin;
}

server {
    location /api/ {
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Vary Origin always;
    }
}

PHP CORS Handler

$allowedOrigins = [
    'https://app.example.com',
    'https://admin.example.com',
];

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
    header("Access-Control-Allow-Origin: $origin");
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization');
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');
    header('Vary: Origin');
}

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

CORS with Credentials

When requests include cookies or authorization headers, additional rules apply:

  • Access-Control-Allow-Credentials: true must be set
  • Access-Control-Allow-Origin cannot be * — it must be the specific origin
  • The client must set credentials: 'include' in the fetch options
// Client-side JavaScript
fetch('https://api.example.com/data', {
    method: 'GET',
    credentials: 'include',  // Send cookies cross-origin
    headers: {
        'Authorization': 'Bearer token123'
    }
});

Common CORS Errors and Solutions

ErrorCauseSolution
No 'Access-Control-Allow-Origin' headerServer does not send CORS headersAdd CORS headers to server response
Origin not allowedServer allows different origin(s)Add your origin to the allowed list
Preflight response is not successfulOPTIONS request returns errorHandle OPTIONS method on the server, return 204
Credentials flag is true but Allow-Origin is *Wildcard not allowed with credentialsSet specific origin instead of wildcard
Request header not allowedCustom header not in Allow-HeadersAdd the header to Access-Control-Allow-Headers

Security Best Practices

  • Never use Access-Control-Allow-Origin: * for authenticated endpoints — it allows any website to make authenticated requests
  • Validate the Origin header server-side — do not blindly reflect it back
  • Limit allowed methods — only allow methods the API actually uses
  • Limit exposed headers — only expose headers the client actually needs
  • Set Access-Control-Max-Age — reduce preflight frequency (86400 = 24 hours)
  • Use Vary: Origin — when dynamically selecting allowed origins, ensure caches distinguish responses by origin

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,
});

CORS and Monitoring

For web monitoring, CORS affects how monitoring tools interact with APIs:

  • Browser-based monitoring (Real User Monitoring) is subject to CORS restrictions
  • Server-side monitoring is not affected by CORS — it is a browser-only mechanism
  • When testing APIs from the browser, CORS errors may mask actual server errors
  • Preflight caching (Max-Age) affects how quickly CORS policy changes take effect

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.

Summary

CORS is a browser security mechanism that controls cross-origin HTTP requests. It uses HTTP headers to determine whether a web page from one origin can access resources from another. Simple requests are sent directly; complex requests trigger a preflight OPTIONS check. Proper CORS configuration requires specifying allowed origins, methods, and headers — and being especially careful with credentialed requests. Understanding CORS is essential for anyone building or consuming web APIs, as misconfiguration leads to either security vulnerabilities or broken functionality.

Check your website right now

Check your site's security →
More articles: Security
Security
How to Check a Website for Malware: 4 Layers and a Cleanup
01.04.2026 · 1 217 views
Security
Cookie Security Flags: HttpOnly, Secure, SameSite
14.03.2026 · 621 views
Security
Web Server Security Hardening Checklist: Nginx and Apache
16.03.2026 · 615 views
Security
How to Check a Website for Fraud: 12 Signs of a Phishing Site
18.07.2026 · 523 views