چطور خطاهای CORS را برطرف کنیم (و دلیل وقوعشان)
خطای CORS یعنی صفحهی وب شما تلاش کرده پاسخی را از یک مبدأ دیگر (دامنه، پورت یا پروتکل دیگر) بخواند، و آن سرور هدرهایی نفرستاده که نشان دهد این کار مجاز است. مرورگرها برای محافظت از کاربران، پاسخ را مسدود میکنند. شما این خطا را روی سرور برطرف میکنید، با برگرداندن هدر Access-Control-Allow-Origin برای مبدأ سایتتان و مدیریت درخواستهای OPTIONS از نوع preflight، یا با فرستادن درخواست از طریق بکاِند خودتان روی همان مبدأ از آن دوری میکنید.
این یکی از گیجکنندهترین خطاها در توسعهی وب است، چون درخواست اغلب به سرور میرسد. این راهنما توضیح میدهد چه اتفاقی میافتد و هر نوع خطا را چطور برطرف کنید.
«مبدأ» یعنی چه
مبدأ ترکیب پروتکل، دامنه و پورت است:
| نشانی | مبدأ |
|---|---|
https://myapp.com/dashboard | https://myapp.com |
https://api.myapp.com/users | https://api.myapp.com (زیردامنهی متفاوت، پس مبدأ متفاوت) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://localhost:3000 (پورت متفاوت، پس مبدأ متفاوت) |
مرورگرها از سیاست هممبدأ (same-origin) پیروی میکنند: جاوااسکریپت یک صفحه آزادانه میتواند پاسخهای مبدأ خودش را بخواند، ولی پاسخهای مبدأهای دیگر را نه. CORS (Cross-Origin Resource Sharing) سازوکاری است که به سرور اجازه میدهد داوطلبانه اعلام کند: «پاسخهای من را صفحات از این مبداها میتوانند بخوانند.»
دو نتیجهی غافلگیرکننده:
- درخواست اغلب به سرور میرسد. CORS معمولاً جلوی خواندن پاسخ توسط کد شما را میگیرد، نه دریافت درخواست توسط سرور. یک POST ممکن است ذخیره شده باشد با اینکه شما خطا دیدهاید.
- فقط مرورگرها آن را اعمال میکنند. curl، Postman و کد سمت سرور CORS را کاملاً نادیده میگیرند. به همین دلیل «در Postman کار میکند» هیچ چیزی دربارهی CORS نمیگوید.
گام ۱: خطای دقیق را بخوانید
ابزارهای توسعهدهنده را باز کنید (راستکلیک → Inspect)، به Console بروید و پیام کامل را بخوانید. متن Chrome مشخص میکند با کدام مشکل روبهرو هستید:
| پیام شامل | مشکل |
|---|---|
No 'Access-Control-Allow-Origin' header is present | سرور اصلاً هدر را نفرستاده، یا درخواست پیش از رسیدن به کد CORS شما شکست خورده (برای مثال یک ۴۰۴ یا ۵۰۰) |
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin | سرور مبدأ دیگری غیر از مبدأ شما را مجاز کرده است |
Response to preflight request doesn't pass access control check | درخواست preflight از نوع OPTIONS شکست خورده |
...must not be the wildcard '*' when the request's credentials mode is 'include' | شما کوکی میفرستید و سرور * پاسخ داده است |
Request header field authorization is not allowed | سرور آن هدر را در Access-Control-Allow-Headers فهرست نکرده |
سپس تب Network را باز کنید، درخواست ناموفق (و هر درخواست OPTIONS پیش از آن) را پیدا کنید و کد وضعیت و هدرهای پاسخ آن را ببینید.
ابتدا کد وضعیت را بررسی کنید. بسیاری از «خطاهای CORS» در واقع یک خطای سرور هستند که خودش را جای دیگری نشان میدهد. اگر سرور کرش کند یا ۴۰۴ برگرداند، پاسخ خطای آن اغلب هدرهای CORS ندارد، پس مرورگر بهجای مشکل واقعی، CORS گزارش میدهد. ۵۰۰ یا ۴۰۴ را اول درست کنید، و ممکن است خطای CORS هم ناپدید شود.
گام ۲: تصمیم بگیرید اصلاً باید از مرورگر این API را صدا بزنید؟
پیش از اضافه کردن هدر، بپرسید: آیا مرورگر باید مستقیم این API را صدا بزند؟
- یک API شخص ثالث که کلید مخفی میخواهد (OpenAI، کلید مخفی Stripe، بیشتر سرویسهای ایمیل) هرگز نباید از مرورگر صدا زده شود. کلید برای هر بازدیدکننده قابلمشاهده میشود. آن را از بکاِند خودتان صدا بزنید؛ آنگاه مشکل CORS از بین میرود، چون درخواستهای سرور به سرور مشمول CORS نیستند. چطور کلیدهای API را امن نگه داریم را ببینید.
- یک API شخص ثالث که کنترلش با شما نیست و هدرهای CORS نمیفرستد، از سمت شما قابل رفع نیست. آن را از بکاِند خودتان صدا بزنید.
- API خودتان را میتوان با تنظیم درست رفع کرد. ادامه دهید.
گام ۳: روی سرورتان رفعش کنید
سرور باید هدرهای درست را به پاسخهایش اضافه کند، از جمله پاسخهای خطا.
برای درخواست واقعی، این را بفرستید:
Access-Control-Allow-Origin: https://myapp.com(مبدأ دقیق فرانتاِند شما، بدون اسلش انتهایی)Vary: Origin، اگر مقدار به مبدأ درخواستکننده بستگی دارد
برای درخواست preflight از نوع OPTIONS، با یک وضعیت موفق (200 یا 204) و اینها پاسخ دهید:
Access-Control-Allow-Origin: https://myapp.comAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-Control-Allow-Headers: Content-Type, Authorization(هر هدر سفارشی که میفرستید)- اختیاری
Access-Control-Max-Age: 600، تا مرورگر preflight را برای مدتی کش کند
بیشتر فریمورکها بسته یا تنظیمی دارند که این کار را انجام میدهد. در Express بستهی cors؛ در FastAPI، CORSMiddleware؛ در ASP.NET Core، AddCors؛ در Spring، @CrossOrigin یا پیکربندی CORS. یک راهاندازی معمول در Express:
import cors from "cors";
app.use(cors({
origin: ["https://myapp.com", "http://localhost:5173"],
credentials: true,
}));
مدیریت CORS را پیش از مسیرها و احراز هویت خود قرار دهید، تا درخواستهای preflight (که ورود ندارند) با ۴۰۱ رد نشوند.
با یک سازندهی هوش مصنوعی، راهاندازی را دقیق توصیف کنید:
فرانتاِند در
https://myapp.comبه API درhttps://api.myapp.comدرخواست میزند و «Response to preflight request doesn't pass access control check» میگیرد. CORS را روی API طوری تنظیم کن که دقیقاًhttps://myapp.comو مبدأ پیشنمایش را مجاز کند، متدهای GET، POST، PATCH و DELETE را با هدرهای Content-Type و Authorization بپذیرد، OPTIONS را پیش از احراز هویت مدیریت کند، و هدرهای CORS را روی پاسخهای خطا هم بگذارد.
گام ۴: کوکیها و اعتبارنامهها را مدیریت کنید
اگر فرانتاِند کوکیها (مثلاً نشست ورود) را به مبدأ دیگری میفرستد، هر دو طرف باید هماهنگ باشند:
- فرانتاِند
credentials: "include"را رویfetchمیگذارد، یاwithCredentials: trueرا در axios. - سرور
Access-Control-Allow-Credentials: trueرا میفرستد. Access-Control-Allow-Originسرور باید مبدأ دقیق باشد، نه*.
کوکیهای بینسایتی به SameSite=None; Secure هم نیاز دارند و برخی مرورگرها کوکیهای شخص ثالث را بیشتر محدود میکنند. این یکی دیگر از دلایلی است که رویکرد گام ۵ را ترجیح دهید.
گام ۵: با یک راهاندازی هممبدأ از CORS دوری کنید
اغلب تمیزترین راهحل این است که اصلاً درخواست بینمبدأی نفرستید. API را از همان مبدأ صفحه سرو کنید، برای مثال در https://myapp.com/api/...:
- در توسعه، بیشتر سرورهای توسعه میتوانند پروکسی کنند.
server.proxyدر Vite میتواند/apiرا به بکاِند روی پورت دیگری بفرستد، پس مرورگر فقط با یک مبدأ حرف میزند. - در محیط عملیاتی،
/apiرا از طریق تنظیمات rewrite یا پروکسی میزبانتان به بکاِند بفرستید، یا فرانتاِند و API را روی یک سرور سرو کنید.
آنگاه فرانتاِند یک مسیر نسبی مثل fetch("/api/orders") را صدا میزند. دیگر درخواست بینمبدأ، preflight بینمبدأ و دردسر کوکی وجود ندارد. همچنین نیازی نیست نشانی API را که بین پیشنمایش و محیط عملیاتی فرق دارد، سختکد کنید.
فرانتاِند را طوری تغییر بده که بکاِند را با مسیرهای نسبی
/apiصدا بزند، نه با یک نشانی کامل، و یک پروکسی توسعه تنظیم کن که/apiرا به سرور API بفرستد. سپس تنظیمات CORS را که دیگر لازم نیستند حذف کن.
کارهایی که نباید انجام دهید
mode: "no-cors"درfetch. چیزی را دور نمیزند؛ به کد شما یک پاسخ خالی و غیرقابلخواندن میدهد.- افزونههای مرورگر که CORS را خاموش میکنند. فقط مرورگر شما را تغییر میدهند. هر بازدیدکننده همچنان خطا را میبیند.
- سرویسهای عمومی «پروکسی CORS» برای اپهای واقعی. میتوانند هر چیزی که از آنها عبور میکند را بخوانند و تغییر دهند، و گاهی از کار میافتند.
Access-Control-Allow-Origin: *روی API ای که ورود دارد. با اعتبارنامه کار نمیکند و پیشفرض اشتباهی برای دادهی خصوصی است.- بازتاب دادن هر هدر
Originبه پاسخ همراه با مجاز بودن اعتبارنامه. این به هر وبسایتی اجازه میدهد درخواستهای ورودیِ کاربران شما را بهجای آنها بفرستد. از فهرست مجاز استفاده کنید.
علتهای رایج در اپهای ساختهشده با هوش مصنوعی
| وضعیت | راهحل |
|---|---|
| فرانتاِند یک API شخص ثالث را با کلید مخفی صدا میزند | فراخوانی را به بکاِند منتقل کنید |
| در پیشنمایش کار میکند، روی دامنهی منتشرشده خراب است | نشانیهای منتشرشده و دامنهی اختصاصی را به فهرست مجاز اضافه کنید، یا از مسیرهای نسبی /api استفاده کنید |
| خطا فقط روی POST، PUT یا DELETE | preflight مدیریت نشده؛ مدیریت OPTIONS و هدرهای مجاز را اضافه کنید |
| خطا بعد از اضافه کردن ورود ظاهر شد | اعتبارنامهها به مبدأ دقیق و Allow-Credentials نیاز دارند |
| «خطای CORS» ولی تب Network کد ۵۰۰ نشان میدهد | ابتدا خطای سرور را برطرف کنید |
نشانی API در کد با localhost سختکد شده | از مسیرهای نسبی یا یک تنظیم جدا برای هر محیط استفاده کنید |
CORS در Mythex
وقتی یک پروژهی Mythex یک وبسایت و یک سرور API دارد، هر دو بهعنوان سرویس در همان پروژه اجرا میشوند و میتوانید در پیشنمایش بینشان جابهجا شوید. از عامل بخواهید که فرانتاِند بکاِند را از طریق مسیر نسبی /api صدا بزند، که در توسعه پروکسی شود، و بیشتر مشکلات CORS از بین میروند. اگر واقعاً به درخواستهای بینمبدأ نیاز دارید، خطای دقیق کنسول و مبداهای درگیر را به عامل بدهید. نتیجه را روی اپ منتشرشده هم بررسی کنید، چون نشانی آن با پیشنمایش فرق دارد. معماری چندسرویسی و بکاِند API را در مستندات ببینید، و راهنماهای ما دربارهی عیبیابی یک اپ ساختهشده با هوش مصنوعی و فرانتاِند در برابر بکاِند.
فهرست بررسی CORS
- خطای کامل کنسول و کد وضعیت درخواست ناموفق را خواندم
- یک ۴۰۴ یا ۵۰۰ پنهان را رد کردم
- APIهایی که کلید مخفی دارند از بکاِند صدا زده میشوند، نه از مرورگر
- سرور
Access-Control-Allow-Originرا با مبداهای مجاز دقیق میفرستد - درخواست preflight از نوع
OPTIONSبا متدها و هدرهای مجاز و پیش از احراز هویت پاسخ میگیرد - هدرهای CORS روی پاسخهای خطا هم وجود دارند
- اعتبارنامهها: مبدأ دقیق،
Allow-Credentials: trueو تنظیمات درست کوکی - مبداهای پیشنمایش، منتشرشده و دامنهی اختصاصی همه پوشش داده شدهاند، یا از مسیرهای نسبی
/apiاستفاده شده - از
no-cors، پروکسیهای عمومی و بازتاب هر مبدأ استفاده نشده است
پرسشها
خطای CORS یعنی چه؟
صفحهی شما که از یک مبدأ بارگذاری شده، تلاش کرده پاسخی از مبدأ دیگری را بخواند، و هدرهای پاسخ سرور آن مبدأ اجازهی این کار را نداده است. مرورگر جلوی خواندن پاسخ توسط کد شما را میگیرد. راهحل روی سرور است یا در نحوهی مسیریابی درخواست، نه در کد فرانتاِند.
آیا میتوانم خطای CORS را از فرانتاِند برطرف کنم؟
درست نه. تنظیماتی مثل mode: 'no-cors' فقط پاسخ را از کد شما پنهان میکنند. درست آن است که سرور هدر Access-Control-Allow-Origin مناسب را بفرستد، یا API را از بکاِند خودتان یا یک پروکسی هممبدأ صدا بزنید.
آیا Access-Control-Allow-Origin: * امن است؟
برای دادهی عمومی که هر کسی میتواند بخواند، مثل یک API عمومی بدون ورود، اشکالی ندارد. با کوکی یا سایر اعتبارنامهها نمیتوان از آن استفاده کرد، و برای API های خصوصی باید مبداهای دقیق مجاز را فهرست کنید.
چرا درخواستم در Postman یا curl کار میکند ولی در مرورگر نه؟
CORS فقط توسط مرورگرها اعمال میشود. ابزارهایی مثل Postman و curl آن را اجرا نمیکنند، پس یک درخواست آنجا موفق است ولی وقتی جاوااسکریپت صفحه آن را میفرستد، مسدود میشود.
درخواست preflight چیست؟
برای درخواستهایی که ساده نیستند، مثل POST از نوع JSON یا درخواستی با هدر Authorization، مرورگر ابتدا یک درخواست OPTIONS میفرستد و از سرور میپرسد کدام متدها و هدرها مجازند. اگر آن درخواست OPTIONS شکست بخورد یا هدرهای درست را نداشته باشد، درخواست اصلی هرگز فرستاده نمیشود.