Access-Control-Allow-Origin and CORS policy errors

Tells the browser which other origins may read this response from JavaScript. Too strict and your front end breaks; too loose and any site can read your users' data.

Updated 3 November 2026

Check your site's CORS header

We send one normal request (following redirects) and grade the Access-Control-Allow-Origin header on the final response.

Recommended value

Access-Control-Allow-Origin: https://app.example.com
Value / directiveWhat it does
https://app.example.com Exactly one origin may read the response. To allow several, check the request Origin against an allowlist and echo the match.
* Any origin may read it, but only without credentials. Fine for public, unauthenticated data.
null Matches sandboxed iframes and local files. Never allow it.

What "blocked by CORS policy" means

Browsers apply the same-origin policy: JavaScript on https://app.example.com cannot read responses from https://api.example.com unless the API says so. When you see

Access to fetch at 'https://api.example.com/user' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

the fix belongs on the server being called (the API), not in the front-end code. The API must answer with Access-Control-Allow-Origin: https://app.example.com, and for non-simple requests (JSON bodies, custom headers, PUT/DELETE) it must also answer the preflight OPTIONS request with Access-Control-Allow-Methods and Access-Control-Allow-Headers.

Credentials

If the front end sends cookies (fetch(url, { credentials: 'include' }), or Axios withCredentials), the response must name the exact origin and send Access-Control-Allow-Credentials: true. A * wildcard is rejected by the browser when credentials are involved.

The dangerous mistake: reflecting any origin

A common shortcut is to copy whatever Origin the request sends into Access-Control-Allow-Origin and add Allow-Credentials: true. That lets any website read your logged-in users' API responses. Our check above sends a made-up Origin and tells you if your server reflects it. Echo the origin only after checking it against an allowlist, and add Vary: Origin so caches do not serve one origin's response to another.

Laravel

Laravel handles CORS with the built-in HandleCors middleware and config/cors.php (publish it with php artisan config:publish cors). Set paths to your API routes, list exact origins in allowed_origins (or careful regexes in allowed_origins_patterns), and only set supports_credentials to true if you use cookie auth such as Sanctum's SPA mode.

How to set Access-Control-Allow-Origin

nginx

# Allowlist, not reflection: only listed origins are echoed back.
map $http_origin $cors_origin {
    default "";
    "https://app.example.com" $http_origin;
}

server {
    location /api/ {
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Access-Control-Allow-Credentials "true" always;
        add_header Vary "Origin" always;
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE" always;
            add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-XSRF-TOKEN" always;
            add_header Vary "Origin" always;
            return 204;
        }
    }
}

Apache

SetEnvIf Origin "^https://app\.example\.com$" CORS_ORIGIN=$0
Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header always set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
Header always merge Vary "Origin"

Laravel middleware

// config/cors.php  (php artisan config:publish cors)
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://app.example.com'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true, // only for cookie-based auth (Sanctum SPA)
];

Express (Node.js)

const cors = require('cors');

app.use('/api', cors({
  origin: ['https://app.example.com'], // never `true` with credentials
  credentials: true,
}));

Cloudflare

Cloudflare transform rules set static values, so they suit a single allowed origin or a public "*" API. For several origins, handle CORS in your application or a Worker that checks the Origin against an allowlist.

In nginx, an add_header inside a location block discards every add_header inherited from the server block. Repeat your security headers there, or use an include file. The always flag also sends the header on error responses.

Common mistakes

  • Reflecting the request Origin with Access-Control-Allow-Credentials: true. Any site can then read authenticated responses.
  • Allowing the null origin. Sandboxed iframes and file:// pages send Origin: null, so attackers can too.
  • Loose regexes like example\.com$ that also match evil-example.com.
  • Trying to fix a CORS error in the front end, or with a browser extension. The server has to send the header.
  • Forgetting Vary: Origin when the header changes per request.

FAQ

How do I fix "blocked by CORS policy: No Access-Control-Allow-Origin header"?

Configure the API you are calling to return Access-Control-Allow-Origin with your front-end origin, and to answer preflight OPTIONS requests. In Laravel, set allowed_origins in config/cors.php.

Can I use Access-Control-Allow-Origin: * with cookies?

No. Browsers reject a wildcard when credentials are sent. Name the exact origin and send Access-Control-Allow-Credentials: true.

Is Access-Control-Allow-Origin: * insecure?

Not for public, unauthenticated data. It is wrong for anything that depends on the user being logged in.

What is a CORS preflight?

An OPTIONS request the browser sends before non-simple requests to ask which methods and headers are allowed. The server must answer it with the Access-Control-Allow-* headers.

Headers are one of 30+ things attackers check

The free StackShield scan also checks for exposed .env files, debug mode, leaked tooling, weak TLS and more, from the outside, in about a minute.

Run a free scan