كيف تصلح أخطاء CORS (ولماذا تحدث)
خطأ CORS يعني أن صفحتك حاولت قراءة استجابة من أصل مختلف (نطاق آخر أو منفذ أو بروتوكول آخر)، ولم يرسل ذلك الخادم ترويسات تقول إن هذا مسموح. يمنع المتصفح الاستجابة لحماية المستخدمين. تصلحه في الخادم، بإرجاع ترويسة Access-Control-Allow-Origin لأصل موقعك ومعالجة طلبات OPTIONS المسبقة، أو تتجنبه بإرسال الطلب عبر خادمك الخلفي على نفس الأصل.
هو من أكثر الأخطاء إرباكاً في تطوير الويب، لأن الطلب غالباً ما يصل إلى الخادم بشكل سليم. يشرح هذا الدليل ما يحدث، وكيف تصلح كل نوع من هذه الأخطاء.
ما معنى «الأصل»
الأصل هو مزيج من البروتوكول والنطاق والمنفذ:
| الرابط | الأصل |
|---|---|
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 policy): يستطيع JavaScript في الصفحة قراءة الاستجابات من أصله بحرية، لكن ليس من الأصول الأخرى. أما CORS (Cross-Origin Resource Sharing، أي مشاركة الموارد بين المصادر) فهو النظام الذي يسمح للخادم بالموافقة صراحةً: «يحق للصفحات القادمة من هذه الأصول قراءة ردودي».
هناك نتيجتان تفاجئان الناس:
- غالباً ما يصل الطلب إلى الخادم. يمنع CORS عادةً كودك من قراءة الاستجابة، ولا يمنع الخادم من استلام الطلب. قد يكون طلب POST قد حُفظ، رغم أنك رأيت خطأً.
- المتصفحات وحدها تفرضه. curl وPostman والكود الذي يعمل على الخادم يتجاهلون CORS تماماً. لهذا السبب لا يعني «يعمل في Postman» شيئاً بخصوص CORS.
الخطوة 1: اقرأ الخطأ كما هو
افتح أدوات المطوّر (انقر بالزر الأيمن ← Inspect أو «فحص»)، واذهب إلى تبويب Console، واقرأ الرسالة كاملة. صياغة Chrome تخبرك بنوع المشكلة:
| الرسالة تحتوي على | ما الخطأ |
|---|---|
No 'Access-Control-Allow-Origin' header is present | لم يرسل الخادم هذه الترويسة أصلاً، أو أن الطلب فشل قبل أن يصل إلى كود CORS (مثلاً خطأ 404 أو 500) |
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 | فشل طلب 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» هي في الحقيقة خطأ في الخادم متخفٍّ. إذا تعطّل الخادم أو أرجع 404، فإن رسالة الخطأ لا تحمل غالباً ترويسات CORS، فيُبلغ المتصفح عن CORS بدلاً من المشكلة الحقيقية. أصلح الخطأ 500 أو 404، وقد يختفي خطأ CORS معه.
الخطوة 2: قرّر إن كان يجب أن يستدعي المتصفح هذه الواجهة البرمجية أصلاً
قبل إضافة الترويسات، اسأل: هل يجب أن يتصل المتصفح بهذه الواجهة مباشرة؟
- واجهة برمجية لطرف ثالث تحتاج مفتاحاً سرياً (مثل OpenAI أو مفتاح Stripe السري أو معظم خدمات البريد) لا يجب أبداً استدعاؤها من المتصفح. فالمفتاح سيكون مرئياً لكل زائر. استدعها من خادمك الخلفي، فتختفي مشكلة CORS، لأن الطلبات بين الخوادم لا تخضع لـ CORS. راجع كيف تحمي مفاتيح الواجهات البرمجية.
- واجهة برمجية لطرف ثالث لا تتحكم فيها ولا ترسل ترويسات CORS، لا يمكن إصلاحها من جهتك. استدعها من خادمك الخلفي.
- واجهتك البرمجية الخاصة يمكن إصلاحها بضبطها بشكل صحيح. تابع أدناه.
الخطوة 3: أصلح الأمر في خادمك
يجب أن يضيف الخادم الترويسات الصحيحة إلى ردوده، بما في ذلك ردود الخطأ.
للطلب الفعلي، أرسل:
Access-Control-Allow-Origin: https://myapp.com(الأصل الدقيق لواجهتك الأمامية، بدون شرطة مائلة في النهاية)Vary: Origin، إذا كانت القيمة تعتمد على الأصل الذي طلب الاستجابة
لطلب 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، ليخزّن المتصفح الطلب المسبق مؤقتاً لفترة
معظم أطر العمل لديها حزمة أو إعداد يقوم بذلك. في 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 قبل مساراتك وقبل التحقق من تسجيل الدخول، حتى لا يُرفض الطلب المسبق (الذي لا يحمل تسجيل دخول) بخطأ 401.
مع منشئ تطبيقات بالذكاء الاصطناعي، صِف الإعداد بدقة:
الواجهة الأمامية على
https://myapp.comتستدعي الواجهة البرمجية علىhttps://api.myapp.comوتحصل على خطأ «Response to preflight request doesn't pass access control check». اضبط CORS على الواجهة البرمجية ليسمح بالأصلhttps://myapp.comوأصل المعاينة فقط، وليسمح بطرق GET وPOST وPATCH وDELETE مع ترويستي Content-Type وAuthorization، وليعالج OPTIONS قبل التحقق من الهوية، وليتضمن ترويسات CORS في ردود الأخطاء أيضاً.
الخطوة 4: تعامل مع ملفات تعريف الارتباط وبيانات الاعتماد
إذا كانت واجهتك الأمامية ترسل ملفات تعريف الارتباط (مثل جلسة تسجيل الدخول) إلى أصل آخر، فيجب أن يتفق الطرفان:
- تضبط الواجهة الأمامية
credentials: "include"فيfetch، أوwithCredentials: trueفي axios. - يرسل الخادم
Access-Control-Allow-Credentials: true. - يجب أن تكون قيمة
Access-Control-Allow-Originلدى الخادم الأصل الدقيق، وليس*.
كذلك تحتاج ملفات تعريف الارتباط عبر المواقع إلى SameSite=None; Secure، وبعض المتصفحات تقيّد ملفات تعريف الارتباط من الأطراف الثالثة أكثر من ذلك. وهذا سبب إضافي لتفضيل الطريقة الواردة في الخطوة 5.
الخطوة 5: تجنّب CORS بإعداد من نفس الأصل
غالباً ما يكون أنظف حل ألا تُجري طلبات عبر أصول مختلفة من الأساس. قدّم الواجهة البرمجية من نفس أصل الصفحة، مثلاً على https://myapp.com/api/...:
- في بيئة التطوير، تستطيع معظم خوادم التطوير عمل وكيل (proxy). يستطيع
server.proxyفي Vite أن يحوّل/apiإلى خادمك الخلفي على منفذ آخر، فلا يتعامل المتصفح إلا مع أصل واحد. - في الإنتاج، وجّه
/apiإلى الخادم الخلفي عبر إعدادات إعادة التوجيه أو الوكيل في منصة الاستضافة، أو قدّم الواجهة الأمامية والواجهة البرمجية من خادم واحد.
عندها تستدعي الواجهة الأمامية مساراً نسبياً مثل fetch("/api/orders"). لا يوجد طلب عبر أصول، ولا طلب مسبق بين الأصول، ولا مشاكل مع ملفات تعريف الارتباط. كما أنك لا تكتب نطاق واجهة برمجية ثابتاً يختلف بين المعاينة والإنتاج.
غيّر الواجهة الأمامية لتستدعي الخادم الخلفي بمسارات /api نسبية بدلاً من رابط كامل، وأعدّ وكيلاً للتطوير يحوّل /api إلى خادم الواجهة البرمجية. ثم احذف أي إعدادات CORS لم نعد نحتاجها.
ما الذي يجب ألا تفعله
mode: "no-cors"فيfetch. لا يتجاوز شيئاً، بل يعطي كودك استجابة فارغة لا يمكن قراءتها.- إضافات المتصفح التي تعطّل CORS. تغيّر متصفحك أنت فقط. كل زائر سيرى الخطأ نفسه.
- خدمات «وكيل CORS» العامة لتطبيقات حقيقية. يمكنها قراءة كل ما يمر عبرها وتعديله، وقد تتوقف عن العمل.
Access-Control-Allow-Origin: *على واجهة تستخدم تسجيل الدخول. لن يعمل مع بيانات الاعتماد على أي حال، وهو خيار افتراضي خاطئ للبيانات الخاصة.- إعادة أي ترويسة
Originكما هي مع السماح ببيانات الاعتماد. هذا يسمح لأي موقع بإرسال طلبات باسم مستخدميك وهم مسجّلو الدخول. استخدم قائمة سماح محددة.
أسباب شائعة في التطبيقات المبنية بالذكاء الاصطناعي
| الحالة | الحل |
|---|---|
| الواجهة الأمامية تستدعي واجهة طرف ثالث بمفتاح سري | انقل الاستدعاء إلى الخادم الخلفي |
| يعمل في المعاينة، ويفشل على النطاق المنشور | أضف أصل النشر ونطاقك المخصص إلى قائمة السماح، أو استخدم مسارات /api نسبية |
| الخطأ يظهر فقط مع POST أو PUT أو DELETE | الطلب المسبق غير معالَج؛ أضف معالجة OPTIONS والترويسات المسموحة |
| ظهر الخطأ بعد إضافة تسجيل الدخول | بيانات الاعتماد تحتاج أصلاً دقيقاً وAllow-Credentials |
| «خطأ CORS» لكن تبويب Network يظهر 500 | أصلح خطأ الخادم أولاً |
رابط الواجهة البرمجية مكتوب بشكل ثابت على localhost | استخدم مسارات نسبية أو إعداداً لكل بيئة |
CORS في Mythex
عندما يكون لمشروع Mythex موقع وخادم واجهة برمجية، يعمل الاثنان كخدمتين في المشروع نفسه، ويمكنك التنقل بينهما في المعاينة. اطلب من الوكيل أن تستدعي الواجهة الأمامية الخادم الخلفي عبر مسار /api نسبي، يُوجَّه عبر وكيل في بيئة التطوير، فهذا يتجنب معظم مشاكل CORS. إذا احتجت فعلاً إلى استدعاءات بين أصول مختلفة، فأعطِ الوكيل نص الخطأ كما يظهر في Console والأصول المعنية. تحقق من النتيجة في التطبيق المنشور أيضاً، لأن عنوانه يختلف عن عنوان المعاينة. راجع التطبيقات متعددة الخدمات والواجهات البرمجية الخلفية في الوثائق، وأدلتنا عن تصحيح تطبيق بُني بالذكاء الاصطناعي والواجهة الأمامية مقابل الخلفية.
قائمة مراجعة CORS
- قرأت رسالة الخطأ الكاملة في Console ورمز حالة الطلب الفاشل
- استبعدت وجود خطأ 404 أو 500 مخفي
- الواجهات التي تحتاج مفتاحاً سرياً تُستدعى من الخادم الخلفي، لا من المتصفح
- يرسل الخادم
Access-Control-Allow-Originمع أصول مسموح بها بدقة - طلب
OPTIONSالمسبق يُجاب بالطرق والترويسات المسموحة، وقبل التحقق من الهوية - ترويسات CORS موجودة في ردود الأخطاء أيضاً
- بيانات الاعتماد: أصل دقيق، و
Allow-Credentials: true، وإعدادات ملفات تعريف الارتباط الصحيحة - أصل المعاينة والنشر والنطاق المخصص مشمولة، أو استُخدمت مسارات /api نسبية
- لا توجد اختصارات مثل
no-corsأو الوكلاء العامين أو إعادة أي أصل كما هو
الأسئلة
ماذا يعني خطأ CORS؟
حاولت صفحتك، المحمّلة من أصل معيّن، قراءة استجابة من أصل مختلف، ولم تتضمن استجابة ذلك الخادم الترويسات التي تسمح بذلك. يمنع المتصفح كودك من قراءة الاستجابة. الحل يكون في الخادم، أو في طريقة توجيه الطلب، وليس في كود الواجهة الأمامية.
هل يمكنني إصلاح خطأ CORS من الواجهة الأمامية؟
ليس بالشكل الصحيح. إعدادات مثل mode: 'no-cors' تخفي الاستجابة عن كودك فقط، ولا تحل المشكلة. الحل هو أن يرسل الخادم ترويسة Access-Control-Allow-Origin الصحيحة، أو أن تستدعي الواجهة البرمجية من خادمك الخلفي الخاص أو من وكيل (proxy) على نفس الأصل.
هل Access-Control-Allow-Origin: * آمن؟
نعم، لبيانات عامة يُسمح للجميع بقراءتها، مثل واجهة برمجية عامة بلا تسجيل دخول. لا يمكن استخدامه مع ملفات تعريف الارتباط (cookies) أو بيانات الاعتماد الأخرى، وبالنسبة للواجهات الخاصة يجب أن تحدد الأصول المسموح بها بدقة.
لماذا يعمل الطلب في Postman أو curl ولا يعمل في المتصفح؟
يطبّق المتصفحات وحدها CORS. أدوات مثل Postman وcurl لا تطبقه، لذا قد ينجح الطلب هناك ويُحظر عندما يرسله JavaScript الخاص بالصفحة.
ما هو الطلب المسبق (preflight)؟
بالنسبة للطلبات غير البسيطة، مثل طلب JSON بصيغة POST أو طلب فيه ترويسة Authorization، يرسل المتصفح أولاً طلب OPTIONS يسأل الخادم عن الطرق والترويسات المسموحة. إذا فشل طلب OPTIONS هذا أو لم يتضمن الترويسات الصحيحة، فلن يُرسل الطلب الفعلي أبداً.