التعامل مع الـ CORS ومشاكل الاتصال بين النطاقات
التعامل مع الـ CORS ومشاكل الاتصال بين النطاقات
عند بناء لوحة تحكم، أداة أتمتة، أو واجهة تعليمية تتصل بخدمات خارجية، ستظهر سريعاً مشكلة شائعة: المتصفح يمنع الطلب رغم أن الخادم يعمل فعلياً. هذه المشكلة ترتبط بسياسة الأمان المعروفة باسم Cross-Origin Resource Sharing أو اختصاراً CORS. فهمها ليس أمراً جانبياً، بل جزء أساسي من أي مشروع يعتمد على الـ API أو على تكاملات الويب الحديثة.
في بيئات الأتمتة، تظهر المشكلة عندما يحاول تطبيق يعمل على نطاق مثل app.example.com الوصول إلى خدمة على نطاق آخر مثل api.vendor.com. هنا لا يحكمنا فقط نجاح الشبكة، بل أيضاً قواعد المتصفح المتعلقة بالأصل Origin والرؤوس Headers وأنواع الطلبات.
ما المقصود بـ CORS ولماذا يوجد أساساً؟
CORS هو آلية أمان يطبقها المتصفح للسماح أو منع الطلبات بين نطاقات مختلفة. الفكرة متفرعة من سياسة Same-Origin Policy التي تمنع صفحة ويب من قراءة استجابات موارد لا تشترك معها في البروتوكول والنطاق والمنفذ.
إذا أردت مراجعة البنية الأساسية للطلبات قبل فهم هذه النقطة بعمق، فستفيدك مقالات مثل فهم بروتوكول HTTP: رحلة البيانات من جهازك إلى السيرفر وتشريح طلب الـ API: الـ Endpoint، الـ Headers، والـ Body. لأن مشكلة CORS لا تعني أن API سيئ، بل أحياناً تعني أن المتصفح لم يحصل على تصريح صريح من الخادم.
تعريف عملي:
الأصلOriginيتكون من:
- البروتوكول مثل
https- اسم النطاق مثل
example.com- المنفذ مثل
3000إذا تغيّر أي عنصر منها، يصبح الطلب بين أصلين مختلفين ويخضع لقواعد
CORS.
كيف يقرر المتصفح السماح أو المنع؟
عندما يرسل المتصفح طلباً إلى خدمة خارجية، فإنه يضيف رأس Origin. ثم ينظر إلى الاستجابة. إذا وجد رأساً مثل Access-Control-Allow-Origin بقيمة مناسبة، يسمح للصفحة بقراءة النتيجة. وإذا لم يجده، أو كانت القيمة لا تطابق الأصل، يمنع الوصول برمجياً حتى لو كان الخادم قد أعاد بيانات صحيحة.
هنا يجب التفريق بين فشل الشبكة وفشل المتصفح في إتاحة الاستجابة. قد ترى في أدوات المطور أن الطلب وصل وعاد 200، لكن كود الواجهة ما زال يعتبره فاشلاً بسبب الحظر. لفهم الأكواد أكثر راجع رموز الحالة (HTTP Status Codes): ماذا يخبرك السيرفر بـ 200 أو 404؟.
الطلبات البسيطة مقابل طلبات Preflight
ليس كل طلب بين نطاقين يُعامل بالطريقة نفسها. بعض الطلبات تعد بسيطة، بينما بعضها يحتاج إلى فحص مسبق يسمى Preflight Request. هذا الفحص يتم عادة عبر طلب من نوع OPTIONS قبل تنفيذ الطلب الحقيقي.
إذا كان طلبك يستخدم رأساً مخصصاً مثل Authorization أو نوع محتوى مثل application/json أو طريقة مثل PUT أو DELETE، فغالباً سيدخل المتصفح مرحلة Preflight. وهذا يرتبط أيضاً بما شرحناه في شرح أفعال الـ HTTP (GET, POST, PUT, DELETE) والفرق بينها.
ما الذي يسأله طلب
OPTIONS؟
هل يُسمح لهذا الأصل بالوصول؟
هل يُسمح بهذه الطريقة مثلPOST؟
هل يُسمح بهذه الرؤوس مثلAuthorizationوContent-Type؟
أشهر أسباب ظهور خطأ CORS
- الخادم لا يعيد رأس
Access-Control-Allow-Origin. - وجود قيمة عامة
*مع استخدام بيانات اعتمادCredentialsمثل الكوكيز، وهذا غير مسموح. - الخادم لا يدعم طلب
OPTIONSأو لا يعيد رؤوس الفحص المطلوبة. - إرسال رؤوس مخصصة غير مدرجة داخل
Access-Control-Allow-Headers. - اختلاف المنفذ في بيئة التطوير بين الواجهة والخلفية.
- الخلط بين مشكلة
AuthenticationومشكلةCORS، خصوصاً عند استخدام OAuth 2.0 أو Bearer Tokens.
مثال عملي من الواجهة الأمامية
السيناريو التالي شائع في لوحات الأتمتة التعليمية: واجهة JavaScript تحاول جلب بيانات دورة من خدمة خارجية عبر REST API. إذا لم يكن الخادم مهيأً، سيظهر الخطأ في وحدة التحكم.
async function fetchCourses() {
try {
const response = await fetch('https://api.example.com/courses', {
method: 'GET',
headers: {
'Authorization': 'Bearer your_access_token',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Request failed:', error.message);
}
}
fetchCourses();
هذا الكود صحيح من حيث الصياغة، لكن نجاحه يعتمد على إعدادات الخادم. وهنا تبرز أهمية فهم نوع البيانات والرؤوس، وهو ما يرتبط كذلك بمقال لغة الـ JSON: كيف تقرأ وتكتب البيانات التي تفهمها الآلات.
كيف نحل المشكلة من جهة الخادم؟
الحل الحقيقي غالباً لا يكون في المتصفح، بل في الخدمة الخلفية أو في الطبقة الوسيطة Proxy. يجب أن يصرّح الخادم بوضوح من يحق له الوصول، وما الطرق والرؤوس المسموح بها.
تهيئة نموذجية في خادم Node.js مع Express
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
app.get('/courses', (req, res) => {
res.json([{ id: 1, title: 'Automation 101' }]);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
ملاحظات مهمة:
إذا استخدمتcredentials: trueفلا تستخدم*فيAccess-Control-Allow-Origin.
بدلاً من ذلك، حدّد النطاقات الموثوقة صراحة.
وفي الأنظمة الإنتاجية، اربط هذه القيم بمتغيرات بيئة آمنة كما شرحنا في أمن البيانات: كيفية تخزين المفاتيح السرية في ملفات .env.
أفضل الممارسات في الأتمتة وتكاملات المنصات
- لا تستدعِ الخدمات الحساسة مباشرة من المتصفح إذا كانت تتطلب مفاتيح سرية أو صلاحيات عالية.
- استخدم طبقة
BackendأوServerless Functionكوسيط. - اختبر نقطة الاتصال أولاً عبر Postman لتعرف هل الخلل من الخدمة أم من المتصفح.
- فرّق بين
APIوWebhook، لأن الويب هوك غالباً لا يواجه نفس نمط الحظر داخل المتصفح. - راقب عدد الطلبات وتكرارها لتجنّب أخطاء إضافية مرتبطة بـ Rate Limiting.
- لا تعتبر تعطيل الأمان في المتصفح أو استخدام إضافات تجاوز مؤقتة حلاً إنتاجياً.
كيف تشخّص المشكلة بسرعة؟
التشخيص الفعّال يبدأ من أدوات المطور داخل المتصفح. افحص تبويب الشبكة Network وراقب هل تم إرسال طلب OPTIONS أولاً. ثم تحقق من الرؤوس العائدة من الخادم. إذا غاب أحد الرؤوس الأساسية، فقد عرفت سبب المشكلة بدقة.
قائمة فحص سريعة:
1. هل الطلب ينجح فيPostmanويفشل في المتصفح؟
2. هل رأسAccess-Control-Allow-Originموجود؟
3. هل الخادم يدعمOPTIONS؟
4. هل الرؤوس المرسلة من الواجهة مدرجة فيAccess-Control-Allow-Headers؟
5. هل تستخدم بيانات اعتماد مع إعدادات أصل عامة؟
الخلاصة
مشكلة CORS ليست خطأ غامضاً بقدر ما هي رسالة واضحة من المتصفح: أثبت أن هذا الاتصال بين النطاقات مصرح به. كلما فهمت العلاقة بين Origin وHeaders وPreflight، أصبحت قادراً على بناء تكاملات مستقرة وآمنة وقابلة للتوسع.
وفي مشاريع الأتمتة الاحترافية، لا يُنظر إلى CORS كمشكلة منفصلة، بل كجزء من تصميم الاتصال نفسه، تماماً كما نختار بين REST و SOAP و GraphQL ونخطط لمسارات المصادقة والأمان وتدفق البيانات منذ البداية.