خطای CORS چیست؟ معنای Blocked by CORS Policy و راه رفع آن

تاریخ انتشار:

17 دقیقه مطالعه

Acar Diveroli
نویسنده: Acar Diveroli
بلوک ORIGIN با کابل به کارت‌های ⁦fetch()⁩، PREFLIGHT، CDN و API وصل است؛ فقط کابل API آبی است و سه کابل دیگر بریده شده‌اند
فهرست مطالب

فرانت‌اند شما روی http://localhost:5173 اجرا می‌شود و API روی http://localhost:3001. API در curl و Postman پاسخ JSON برمی‌گرداند، اما در مرورگر fetch() خطای TypeError: Failed to fetch می‌دهد و کنسول این پیام را چاپ می‌کند: Access to fetch at 'http://localhost:3001/api/products' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. با این حال لاگ API نشان می‌دهد درخواست رسیده و با 200 پاسخ گرفته است.

در ادامه می‌بینید مبدأ (origin) چیست، چرا مرورگر پاسخ را مسدود می‌کند، درخواست ساده با درخواست پیش‌پرواز (preflight) چه فرقی دارد و هر پیام Chrome چه می‌گوید. سپس به راه‌حل‌های آزموده‌شده برای Express، Flask و Vite می‌رسیم، به یک مسیر سمت سرور برای APIهایی که در اختیار شما نیستند و به این پرسش که چرا کد وب اسکرپینگ هرگز با CORS روبه‌رو نمی‌شود.

خطای CORS چیست؟

خطای CORS یعنی مرورگر از سپردن یک پاسخ به اسکریپت شما خودداری می‌کند. CORS، کوتاه‌شده Cross-Origin Resource Sharing (اشتراک منابع میان مبدأها)، مجموعه‌ای از هدرهای پاسخ HTTP است که سرور با آن‌ها به مرورگر می‌گوید کدام مبدأهای دیگر اجازه خواندن پاسخ‌هایش را دارند. استاندارد Fetch این سازوکار را طوری تعریف می‌کند که سرور باید صریحاً اجازه بدهد، تا داده‌هایی که پشت فایروال یا صفحه ورود هستند به‌طور پیش‌فرض به سایت‌های دیگر نشت نکنند. اگر هدرهای منطبق در کار نباشد، مرورگر قاعده پیش‌فرض خود، یعنی سیاست هم‌مبدأ (same-origin policy)، را اجرا می‌کند و جلوی خواندن را می‌گیرد.

از این تعریف سه نتیجه به دست می‌آید. لاگ‌های سرور عادی به نظر می‌رسند، چون خطا را مرورگر ساخته است. کد شما فقط یک خطای کلی دریافت می‌کند، TypeError: Failed to fetch از fetch() یا AxiosError: Network Error (با کد ERR_NETWORK) از Axios، و علت فقط در کنسول دیده می‌شود. و راه‌حل در سروری است که پاسخ می‌دهد، نه در کدی که درخواست می‌فرستد.

مبدأ چیست و سیاست هم‌مبدأ جلوی چه چیزی را می‌گیرد؟

مبدأ ترکیب پروتکل (scheme)، میزبان و پورت یک URL است. دو URL فقط وقتی هم‌مبدأ هستند که هر سه یکی باشند:

  • http://localhost:5173 و http://localhost:3001: پورت‌ها متفاوت‌اند، پس مبدأها هم متفاوت‌اند.
  • http://example.com و https://example.com: پروتکل‌ها متفاوت‌اند.
  • https://example.com و https://api.example.com: میزبان‌ها متفاوت‌اند؛ هر زیردامنه مبدأ جداگانه‌ای است.
  • https://example.com/shop و https://example.com/api: هم‌مبدأ؛ مسیر (path) به حساب نمی‌آید.

سیاست هم‌مبدأ همچنان اجازه می‌دهد صفحه تصویر، اسکریپت و فایل استایل سایت‌های دیگر را در خود جای دهد، به آن‌ها لینک بدهد و فرم به آن‌ها بفرستد. آنچه جلویش را می‌گیرد خواندن است: اسکریپت نمی‌تواند پاسخی را که از مبدأ دیگری آمده بخواند، مگر آنکه آن مبدأ اجازه دهد (راهنمای MDN درباره سیاست هم‌مبدأ). CORS راهی است که سرور با آن این اجازه را می‌دهد.

درخواست میان‌مبدأ گام‌به‌گام چگونه کار می‌کند؟

  1. صفحه شما در مبدأ A تابع fetch() را برای یک URL در مبدأ B فراخوانی می‌کند.
  2. مرورگر بررسی می‌کند که درخواست «ساده» است یا نه. اگر نباشد، نخست یک درخواست پیش‌پرواز می‌فرستد (بخش بعد).
  3. مرورگر هدر Origin را اضافه می‌کند، مثلاً Origin: http://localhost:5173. اسکریپت‌ها نمی‌توانند آن را تنظیم یا حذف کنند.
  4. سرور کد خود را اجرا می‌کند و پاسخ می‌دهد. هر کاری که کد قرار بود انجام دهد، در این لحظه انجام شده است.
  5. مرورگر مقدار Access-Control-Allow-Origin در پاسخ را با مبدأ صفحه مقایسه می‌کند. تطابق دقیق را می‌پذیرد، یا * را، به شرط آنکه اطلاعات ورود (credentials) مانند کوکی همراه درخواست نرفته باشد.
  6. اگر تطابق باشد، پاسخ به کد شما می‌رسد؛ وگرنه مرورگر آن را دور می‌اندازد، promise رد (reject) می‌شود و کنسول علت را می‌نویسد.

گامی که اغلب نادیده گرفته می‌شود گام 4 است: CORS جلوی رسیدن درخواست به سرور را نمی‌گیرد و curl، اسکریپت‌ها و سرورهای دیگر گام 5 را اصلاً اجرا نمی‌کنند. CORS از بازدیدکنندگان محافظت می‌کند تا صفحه‌ای که باز می‌کنند نتواند با کوکی‌های خودشان داده‌هایشان را در سایت‌های دیگر بخواند؛ از API شما محافظت نمی‌کند.

درخواست ساده و درخواست پیش‌پرواز: چرا GET کار می‌کند اما POST نه؟

درخواستی «ساده» است که از متد GET، HEAD یا POST استفاده کند و فقط هدرهای مجاز را داشته باشد: Accept، Accept-Language، Content-Language، Range و Content-Type با یکی از مقدارهای application/x-www-form-urlencoded، multipart/form-data یا text/plain. هر درخواست دیگری پیش‌پرواز می‌گیرد: یک درخواست OPTIONS با هدرهای Access-Control-Request-Method و Access-Control-Request-Headers که مرورگر فقط در صورتی پس از آن درخواست اصلی را می‌فرستد که پاسخش اجازه دهد (راهنمای CORS در MDN). بدنه JSON، هدر Authorization، متدهای PUT، PATCH و DELETE یا هدری سفارشی مانند X-Request-ID همگی پیش‌پرواز را به راه می‌اندازند.

از صفحه‌ای روی پورت 5173 یک GET ساده و یک POST با بدنه JSON به یک API روی پورت 3001 فرستادیم که هیچ هدر CORS ارسال نمی‌کند. لاگ API این بود:

text
[no-cors-api] GET /api/products origin=http://localhost:5173
[no-cors-api] OPTIONS /api/products origin=http://localhost:5173

GET با 200 و خود داده پاسخ گرفت و مرورگر باز هم آن را مسدود کرد. از POST فقط پیش‌پرواز به سرور رسید؛ پیش‌پرواز شکست خورد و POST هرگز فرستاده نشد. اما یک POST میان‌مبدأ ساده، مانند ارسال فرمی با کدگذاری application/x-www-form-urlencoded، به سرور می‌رسد و اجرا می‌شود، هرچند صفحه خطا می‌بیند. پس از چنین عملیاتی با احراز هویت و توکن‌های CSRF محافظت کنید، نه با CORS.

هدر Access-Control-Max-Age به مرورگر اجازه می‌دهد پاسخ پیش‌پرواز را کش کند. مقدار پیش‌فرض در استاندارد Fetch پنج ثانیه است؛ Chromium این مقدار را حداکثر تا 2 ساعت و Firefox تا 24 ساعت می‌پذیرد.

پیام‌های blocked by CORS policy هر کدام چه معنایی دارند؟

هر پیام Chrome با Access to fetch at '<URL>' from origin '<origin>' has been blocked by CORS policy: شروع می‌شود، یا برای Axios و XMLHttpRequest با Access to XMLHttpRequest at. Chromium این متن‌ها را در کد منبع خود به انگلیسی ثابت کرده است و در رابط فارسی هم به انگلیسی دیده می‌شوند. متن پس از دونقطه علت را می‌گوید. پنج ردیف نخست جدول زیر را در آزمون خود با یک مرورگر ⁦Chromium 152⁩ عیناً دیدیم؛ بقیه از کد منبع Chromium آمده‌اند.

پیام پس از blocked by CORS policy:معناراه‌حل
No 'Access-Control-Allow-Origin' header is present on the requested resource.هدر CORS وجود ندارد: تنظیم نشده یا یک صفحه خطا پاسخ داده استبه مبدأ خود اجازه دهید؛ کد وضعیت واقعی را بررسی کنید
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present…پاسخ OPTIONS هدر CORS نداشت؛ درخواست اصلی فرستاده نشدبه OPTIONS با هدرهای CORS پاسخ دهید
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.کوکی فرستاده شد و سرور با * پاسخ دادمبدأ دقیق به‌همراه Access-Control-Allow-Credentials: true
Request header field x-debug is not allowed by Access-Control-Allow-Headers in preflight response.هدری که می‌فرستید مجاز نیستآن را مجاز کنید یا دیگر نفرستید
Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.متد مجاز نیستمتد را اضافه کنید
The 'Access-Control-Allow-Origin' header has a value '…' that is not equal to the supplied origin.مبدأ دیگری مجاز شده است: پورت نادرست، http، اسلش پایانیفهرست مجاز را اصلاح کنید
The 'Access-Control-Allow-Origin' header contains multiple values '…', but only one is allowed.دو لایه هدر را اضافه می‌کنند، مثلاً برنامه و nginxآن را فقط در یک جا تنظیم کنید
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.OPTIONS پاسخ 401، 404، 405 یا 500 گرفتبگذارید OPTIONS پیش از احراز هویت عبور کند
Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.OPTIONS تغییر مسیر داده شد، مثلاً به https یا صفحه ورودURL نهایی را فراخوانی کنید

نسخه‌های قدیمی‌تر Chrome جمله‌ای اضافه می‌کردند که mode: 'no-cors' را پیشنهاد می‌داد؛ Chromium در مارس 2025 آن را حذف کرد، چون افراد را گمراه می‌کرد (دلیلش را در پرسش‌های متداول آورده‌ایم). Firefox همین وضعیت را چنین می‌نویسد: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at … (Reason: CORS header 'Access-Control-Allow-Origin' missing).

علت واقعی را چگونه پیدا کنید؟

ابزارهای توسعه‌دهنده مرورگر را با کلید F12 باز کنید و سطر زبانه Console (در رابط فارسی: کنسول) را بخوانید: URL، مبدأ و علت. در زبانه Network (شبکه)، ستون Status (وضعیت) برای درخواست مسدودشده عبارت CORS error را نشان می‌دهد (در رابط فارسی: «خطای CORS»). فراخوانی‌ای که پیش‌پرواز داشته یک ردیف جداگانه OPTIONS هم دارد که در ستون Initiator (آغازگر) آن Preflight نوشته شده است (در رابط فارسی: «پیش‌پرواز»). برای دیدن هدرها روی آن کلیک کنید.

کد وضعیت واقعی به‌آسانی از چشم می‌افتد. در آزمون ما یک دروازه (gateway) بدون هدرهای CORS پاسخ 502 داد: کنسول فقط سطر No 'Access-Control-Allow-Origin' header را نشان داد، در حالی که curl -i روی همان URL عبارت HTTP/1.1 502 Bad Gateway را چاپ کرد. صفحه‌های خطای nginx، متعادل‌کننده‌های بار یا برنامه‌ای که از کار افتاده به‌ندرت هدرهای CORS دارند، برای همین قطعی‌ها شبیه مشکل CORS به نظر می‌رسند. دستور add_header در nginx فقط روی پاسخ‌های 200، 201، 204، 206، 301، 302، 303، 304، 307 و 308 اعمال می‌شود، مگر آنکه پارامتر always را اضافه کنید (مستندات nginx).

اگر API مال خودتان است، خطای CORS را چگونه رفع کنید؟

هدرها را از خود API و برای مبدأهای دقیق فرانت‌اند خود بفرستید. هر قطعه کد را در 6 اکتبر 2026 با ⁦Node.js 24.11⁩، ⁦Express 5.2.1⁩، ⁦cors 2.8.6⁩، ⁦Vite 8.3.3⁩، ⁦Python 3.13⁩، ⁦Flask 3.1.3⁩ و ⁦Flask-CORS 6.0.5⁩ آزمودیم.

Express

با npm install express cors نصب کنید و در package.json مقدار "type": "module" را قرار دهید.

js
// server.js: an API that allows one front end (Express 5, cors 2.8)
import express from "express";
import cors from "cors";

const app = express();

app.use(cors({
  origin: ["https://app.example.com", "http://localhost:5173"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 600,
}));
app.use(express.json());

app.get("/api/products", (req, res) => {
  res.json([{ id: 1, name: "Desk lamp" }]);
});

app.post("/api/products", (req, res) => {
  res.status(201).json({ created: req.body });
});

app.listen(3000, () => console.log("API on http://localhost:3000"));

اگر این میان‌افزار (middleware) با app.use() پیش از مسیرها ثبت شود، به همه درخواست‌های پیش‌پرواز OPTIONS هم پاسخ می‌دهد (مستندات میان‌افزار cors در Express). مبدأها را دقیقاً همان‌طور بنویسید که مرورگر می‌فرستد، بدون اسلش پایانی. مبدأهای دیگر هیچ هدر Access-Control-Allow-Origin دریافت نمی‌کنند و هدف هم همین است. credentials: true را فقط وقتی نگه دارید که فرانت‌اند با credentials: "include" کوکی بفرستد.

پیش‌پرواز را از ترمینال بررسی کنید:

bash
curl -i -X OPTIONS http://localhost:3000/api/products \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type"

سطرهای مربوط از اجرای ما:

text
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Max-Age: 600

Vary: Origin به کش‌ها و CDNها می‌گوید پاسخ به هدر Origin بستگی دارد. با Origin: https://evil.example، همان دستور باز هم 204 برمی‌گرداند، اما بدون سطر Access-Control-Allow-Origin.

Flask

با pip install flask flask-cors نصب کنید.

python
# app.py: the same API in Flask 3 with Flask-CORS 6
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(
    app,
    resources={r"/api/*": {"origins": ["https://app.example.com", "http://localhost:5173"]}},
    allow_headers=["Content-Type", "Authorization"],
    supports_credentials=True,
    max_age=600,
)


@app.get("/api/products")
def list_products():
    return jsonify([{"id": 1, "name": "Desk lamp"}])


@app.post("/api/products")
def create_product():
    return jsonify({"created": request.get_json()}), 201


if __name__ == "__main__":
    app.run(port=5000)

در اجرای ما پاسخ پیش‌پرواز 200 بود و مبدأ، متدها و Vary: Origin را داشت و POST صفحه پاسخ 201 گرفت. همراه supports_credentials=True همیشه فهرستی از مبدأها بدهید: بدون آن، ⁦Flask-CORS 6.0.5⁩ یک مبدأ دلخواه، یعنی https://evil.example، را همراه Access-Control-Allow-Credentials: true به ما برگرداند.

nginx، دروازه‌ها و CDNها

اگر به‌جای برنامه، یک ریورس پروکسی هدرها را تنظیم می‌کند، به هر add_header پارامتر always را بدهید تا پاسخ‌های خطا هم آن‌ها را داشته باشند، و بگذارید فقط یک لایه آن‌ها را تنظیم کند. وقتی اطلاعات ورود را مجاز کرده‌اید، هرگز هر Origin ورودی را عیناً در پاسخ کپی نکنید: در آن صورت هر وب‌سایتی می‌تواند با کوکی‌های کاربران شما داده‌هایشان را بخواند. مبدأها را با یک فهرست ثابت مقایسه کنید.

محیط توسعه: درخواست‌های API را از سرور توسعه عبور دهید

در محیط توسعه، ساده‌ترین راه‌حل این است که API را هم‌مبدأ کنید. سرور توسعه Vite می‌تواند هر مسیری را که با /api شروع می‌شود به API برساند:

js
// vite.config.js: in development, /api goes through Vite to the API
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    port: 5173,
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
      },
    },
  },
});

فرانت‌اند fetch("/api/products") را بدون نام میزبان فراخوانی می‌کند. مرورگر مبدأ خودش را می‌بیند، پس بررسی CORS اجرا نمی‌شود و Vite درخواست را به پورت 3000 می‌رساند؛ در آزمون ما GET پاسخ 200 و POST پاسخ 201 گرفت. گزینه changeOrigin هدر Host را برابر مقصد قرار می‌دهد. webpack-dev-server همین کار را با devServer.proxy انجام می‌دهد. در محیط عملیاتی، فرانت‌اند و API را از طریق وب‌سرور خود زیر یک مبدأ ارائه کنید یا همان تنظیم CORS بالا را نگه دارید.

وقتی API مال شما نیست: آن را از بک‌اند خود فراخوانی کنید

API شخص ثالثی که هدر CORS ندارد معمولاً برای سرورها ساخته شده است، اغلب به این دلیل که کلید محرمانه آن هرگز نباید به مرورگر برسد. یک مسیر به بک‌اند خود اضافه کنید: مرورگر مبدأ شما را فراخوانی می‌کند و سرور شما API را.

js
// relay.js: your backend calls the third-party API; the browser only talks to you
import express from "express";

const app = express();
const PARTNER_URL = "https://api.partner.example/v1/products";

app.get("/api/partner-products", async (req, res) => {
  try {
    const r = await fetch(PARTNER_URL, {
      headers: { Authorization: `Bearer ${process.env.PARTNER_API_KEY}` },
      signal: AbortSignal.timeout(10_000),
    });
    res.status(r.status).type(r.headers.get("content-type") ?? "application/json");
    res.send(await r.text());
  } catch (err) {
    res.status(502).json({ error: "partner API unreachable", detail: err.cause?.code ?? err.name });
  }
});

app.listen(8080, () => console.log("relay on http://localhost:8080"));

این مسیر را با یک سرور محلی آزمودیم که جای API شریک را گرفته بود و هدر CORS نمی‌فرستاد: JSON با 200 برگشت. مسیر را از مبدأ صفحه ارائه کنید؛ کلید در یک متغیر محیطی روی سرور می‌ماند. کار با هدرها، بدنه درخواست و احراز هویت با fetch سمت سرور و Axios در معادل cURL در JavaScript توضیح داده شده است.

مقصد را ثابت نگه دارید. مسیری که هر آدرسی را که در ?url= می‌رسد فراخوانی کند، سرور شما را به یک پروکسی باز تبدیل می‌کند که هر کسی می‌تواند از آن استفاده کند، حتی علیه آدرس‌های داخلی‌ای که فقط سرور شما به آن‌ها دسترسی دارد. شرایط استفاده و محدودیت نرخ API همچنان برقرار است: جای فراخوانی عوض شده، قواعد نه.

چرا پروکسی‌های CORS عمومی خطرناک‌اند؟

یک پروکسی CORS عمومی URL را به‌جای شما فراخوانی می‌کند و Access-Control-Allow-Origin: * را به پاسخ اضافه می‌کند. خطا ناپدید می‌شود، اما:

  • گرداننده آن URL کامل، همه هدرها از جمله کلیدهای API و توکن‌ها، و خود پاسخ را می‌بیند.
  • می‌تواند پاسخ را تغییر دهد و صفحه شما به آن اعتماد می‌کند.
  • درخواست‌های کاربران شما از شرکتی می‌گذرد که هیچ قراردادی با آن ندارید.
  • محدودیت‌های نرخ و قطعی‌های آن به مشکل شما تبدیل می‌شود.

CORS و وب اسکرپینگ: چرا اسکریپت Python یا Node.js شما هرگز با آن روبه‌رو نمی‌شود؟

CORS فقط در مرورگرها وجود دارد. همان API را که مرورگر مسدود کرده بود، این بار از ⁦Node.js 24⁩ فراخوانی کردیم:

js
// The same request from Node.js: no browser, no CORS check
const r = await fetch("http://localhost:3001/api/products");
console.log(r.status, r.headers.get("access-control-allow-origin"), await r.text());
text
200 null [{"id":1,"name":"Desk lamp"}]

هدر Access-Control-Allow-Origin وجود ندارد و Node.js باز هم پاسخ را می‌خواند؛ کتابخانه Requests در Python و curl هم همین رفتار را دارند. اگر می‌کوشید با fetch() در کنسول مرورگر یا یک برنامه فرانت‌اند داده جمع کنید، خطای CORS به شما می‌گوید جای این کار در کد سمت سرور است. اینکه کدام زبان برای آن مناسب‌تر است در اسکرپینگ وب: JavaScript یا Python؟ مقایسه شده است. قواعد سایت آنجا هم برقرار است: از robots.txt پیروی کنید، سرعت درخواست‌ها را پایین نگه دارید و اگر API رسمی وجود دارد از آن استفاده کنید.

پروکسی خطای CORS را رفع نمی‌کند. مرورگر مبدأ صفحه را با هدر Access-Control-Allow-Origin مقایسه می‌کند و آدرس IP بخشی از این بررسی نیست؛ پس پروکسی مسکونی (رزیدنتال)، پروکسی دیتاسنتر یا VPN چیزی را تغییر نمی‌دهد. جای پروکسی در کلاینت HTTP کد سمت سرور شماست، همان‌طور که در استفاده از پروکسی در Node.js نشان داده‌ایم.

برای جمع‌آوری مجاز داده‌ای که به نتایج محلی در کشورها و شهرهای مختلف نیاز دارد، پروکسی مسکونی درخواست‌ها را با هدف‌گیری کشور و شهر از طریق اتصال‌های خانگی می‌فرستد.

برای درخواست‌های پرحجم به APIهای عمومی و صفحه‌هایی با حفاظت سبک، پروکسی دیتاسنتر آدرس‌های ⁦IPv4⁩ اختصاصی با ترافیک بدون سهمیه ارائه می‌دهد.

چه کسانی با خطای CORS روبه‌رو می‌شوند؟

  • توسعه‌دهندگان فرانت‌اند که برنامه و API آن‌ها روی پورت‌های متفاوت اجرا می‌شود.
  • برنامه‌های تک‌صفحه‌ای که یک API شخص ثالث را مستقیم از مرورگر فراخوانی می‌کنند.
  • تیم‌هایی که API را به زیردامنه‌ای مانند api.example.com یا به https منتقل می‌کنند.
  • فرانت‌اندهایی که با ابزارهای هوش مصنوعی تولید شده‌اند و API را مستقیم فراخوانی می‌کنند، گاهی با کلیدی که درون صفحه گذاشته شده است.

اشتباه‌های رایج

  • افزودن Access-Control-Allow-Origin به درخواست. این یک هدر پاسخ است؛ در fetch() به هدری سفارشی تبدیل می‌شود که خودش پیش‌پرواز را به راه می‌اندازد.
  • استفاده از mode: "no-cors". پاسخ مات (opaque) برمی‌گردد: کد وضعیت 0 و بدنه‌ای که نمی‌توان آن را خواند.
  • افزونه‌های allow CORS یا خاموش کردن امنیت وب مرورگر. فقط مرورگر خود شما را تغییر می‌دهد، و چنین افزونه‌ای می‌تواند صفحه‌هایی را که روی آن‌ها اجرا می‌شود بخواند.
  • پاسخ * در حالی که فرانت‌اند کوکی می‌فرستد. مرورگر این ترکیب را رد می‌کند.
  • app.options("*", cors()) در ⁦Express 5⁩. برنامه هنگام راه‌اندازی با PathError [TypeError]: Missing parameter name at index 1: * متوقف می‌شود؛ app.use(cors()) خودش به پیش‌پروازها پاسخ می‌دهد.
  • میان‌افزار احراز هویتی که OPTIONS را رد می‌کند. درخواست‌های پیش‌پرواز توکن ندارند، پس با خطای It does not have HTTP ok status شکست می‌خورند.

راهنمای انتخاب

وضعیتچه کنید
فرانت‌اند و API در محیط توسعه روی پورت‌های متفاوت‌اندپروکسی سرور توسعه (server.proxy در Vite)
API خودتان که از فرانت‌اند خودتان فراخوانی می‌شودمبدأهای دقیق را مجاز کنید؛ به OPTIONS پاسخ دهید
فرانت‌اند کوکی می‌فرستدمبدأ دقیق، Access-Control-Allow-Credentials: true، Vary: Origin
پیام is not allowed برای یک هدر یا متدآن را به Access-Control-Allow-Headers یا -Methods اضافه کنید
خطا پس از استقرار (deploy) یا زیر بار ظاهر می‌شودکد وضعیت واقعی را با curl بررسی کنید؛ always در nginx
API شخص ثالث بدون هدر CORSاز بک‌اند خود فراخوانی کنید و کلید را روی سرور نگه دارید
جمع‌آوری داده از سایت‌های دیگرکد سمت سرور؛ CORS آنجا اعمال نمی‌شود

پرسش‌های متداول

چرا Postman یا curl کار می‌کند اما مرورگر خطای CORS می‌دهد؟

فقط مرورگرها CORS را اجرا می‌کنند. سرور به همه کلاینت‌ها همان پاسخ را می‌فرستد و فقط مرورگر پیش از سپردن آن به اسکریپت، Access-Control-Allow-Origin را بررسی می‌کند. فراخوانی موفق curl ثابت می‌کند API کار می‌کند، نه اینکه مرورگر اجازه خواندن پاسخ آن را دارد.

چرا روی localhost خطای CORS می‌گیرم؟

پورت متفاوت یعنی مبدأ متفاوت: localhost:5173 و localhost:3000 دو مبدأ روی یک دستگاه‌اند. در API به مبدأ فرانت‌اند اجازه دهید یا از پروکسی سرور توسعه استفاده کنید. جدا از این، از ⁦Chrome 142⁩ به بعد، وب‌سایتی عمومی که localhost یا دستگاهی در شبکه خانگی شما را فراخوانی کند به اجازه شما نیاز دارد؛ اگر اجازه ندهید، کنسول از رد شدن دسترسی به فضای آدرس loopback یا local خبر می‌دهد.

آیا ⁦mode: "no-cors"⁩ خطای CORS را رفع می‌کند؟

نه. درخواست فرستاده می‌شود، اما مرورگر یک پاسخ مات برمی‌گرداند؛ در آزمون ما کد وضعیت 0 بود، نوع پاسخ opaque و بدنه خواندنی نبود. این حالت فقط برای درخواست‌هایی مناسب است که هرگز پاسخشان را نمی‌خوانید.

آیا CORS یک سازوکار امنیتی برای API من است؟

نه. CORS جلوی این را می‌گیرد که یک سایت با کوکی‌های بازدیدکننده داده‌های او را در سایت دیگری بخواند، اما هر کسی همچنان می‌تواند API شما را با یک اسکریپت فراخوانی کند. از API با احراز هویت، کنترل دسترسی (authorization) و محدودیت نرخ محافظت کنید.

آیا پروکسی یا VPN خطای CORS را رفع می‌کند؟

نه. مرورگر مبدأ صفحه را با هدر Access-Control-Allow-Origin در پاسخ مقایسه می‌کند؛ آدرس IP بخشی از این بررسی نیست.

چرا فقط درخواست POST من شکست می‌خورد اما GET کار می‌کند؟

POST احتمالاً به خاطر بدنه JSON، هدر Authorization یا یک هدر سفارشی به پیش‌پرواز نیاز دارد. اگر API به درخواست OPTIONS درست پاسخ ندهد، مرورگر هرگز POST را نمی‌فرستد. در زبانه Network دنبال ردیف OPTIONS بگردید یا پیش‌پرواز را با curl بفرستید.

خلاصه

خطای CORS یعنی مرورگر حاضر نیست پاسخ مبدأ دیگری را به اسکریپت شما بدهد؛ درخواست معمولاً به سرور رسیده است. علت را در کنسول بخوانید، کد وضعیت واقعی را با curl بررسی کنید و مشکل را در سرور رفع کنید: مبدأهای دقیق، پاسخ به OPTIONS، متدها و هدرهای درست و Vary: Origin. در زمان توسعه از پروکسی سرور توسعه و برای APIهایی که در اختیارتان نیست از بک‌اند خودتان استفاده کنید. جمع‌آوری داده جایش در کد سمت سرور است، جایی که CORS اعمال نمی‌شود؛ انواع پروکسی برای این کار در صفحه خدمات پروکسی ما آمده‌اند.

پرسش از ChatGPTپرسش از Claude