ما هو CORS؟ وكيف يضبط قراءة استجابات الخادم؟

الكاتب: تاريخ النشر

باختصار: CORS (Cross-Origin Resource Sharing)، أي مشاركة الموارد عبر الأصول، آلية تتيح للخادم السماح للمتصفح بإتاحة استجابته لبرنامج نصي يعمل من أصل مختلف. تحدد ترويسات استجابة الخادم هذا الإذن، ويتولى المتصفح فرضه. لذلك، يشير خطأ CORS عادةً إلى مشكلة في صلاحية قراءة الاستجابة، وليس بالضرورة إلى تعذّر الاتصال بالخادم.

لماذا يقيّد المتصفح الوصول بين الأصول المختلفة

تطبّق المتصفحات سياسة الأصل الواحد (same-origin policy). تحدّ هذه السياسة من قدرة البرنامج النصي على قراءة بيانات من أصل آخر. وبذلك، لا تستطيع صفحة مفتوحة في علامة تبويب جلب البيانات الخاصة وقراءتها بحرية من خدمات أخرى سجّل المستخدم دخوله إليها.

الأصل (origin) هو مجموعة من ثلاثة عناصر: البروتوكول واسم المضيف والمنفذ. ويكفي تغيير أحدها ليصبح الأصل مختلفًا. أما المسار الذي يلي اسم المضيف فلا يؤثر في ذلك.

بالنسبة إلى الصفحة https://matjar.example، تكون المقارنة كما يلي:

العنوان المستهدفهل الأصل واحد؟السبب
https://matjar.example/muntajatنعمالتغيير في المسار فقط
https://api.matjar.exampleلااسم المضيف مختلف
http://matjar.exampleلاالبروتوكول مختلف
https://sklep.example:8443لاالمنفذ مختلف عن المنفذ الافتراضي 443

يتيح CORS للخادم تخفيف هذه القيود بصورة مضبوطة. وهو ليس أسلوب طلب في HTTP مثل GET أو POST، بل آلية تعتمد على ترويسات HTTP.

مثال بسيط: متجر يجلب قائمة المنتجات

لنفترض أن الموقع يعمل على العنوان https://matjar.example، وأن API، أي الواجهة التي تتيح بيانات التطبيق، تعمل على https://api.matjar.example. يريد البرنامج النصي في الصفحة جلب المنتجات:

fetch('https://api.sklep.example/produkty')
  .then(istijaba => istijaba.json())
  .then(muntajat => console.log(muntajat));

بما أن العنوانين من أصلين مختلفين، يضيف المتصفح إلى الطلب ترويسة تحدد أصل الصفحة:

Origin: https://sklep.example

يمكن لخادم API الرد بالبيانات مع ترويسة تمنح الإذن:

Access-Control-Allow-Origin: https://sklep.example

يقارن المتصفح هذه القيمة بأصل الصفحة. إذا كان الإذن مطابقًا، يتيح الاستجابة للبرنامج النصي. أما إذا كانت الترويسة غائبة أو تشير إلى عنوان آخر، فلن يتمكن البرنامج النصي من قراءة الاستجابة، حتى لو أعاد الخادم الحالة 200 OK.

يجب أن تأتي ترويسة الإذن من خادم API. فإضافة Access-Control-Allow-Origin إلى شفرة الطلب في جانب المتصفح لا تحل المشكلة.

متى يرسل المتصفح OPTIONS أولًا

لا تُرسل جميع الطلبات بين الأصول المختلفة مباشرةً. ففي بعض الحالات، ينفّذ المتصفح أولًا فحصًا تمهيديًا (preflight)، أي طلبًا أوليًا للتحقق من إذن الخادم.

على سبيل المثال، يؤدي إرسال طلب شراء بأسلوب POST ونوع محتوى application/json إلى هذه الخطوة. وكذلك الحال عند استخدام ترويسة Authorization. وقد يبدو الطلب التمهيدي كما يلي:

OPTIONS /talabat HTTP/1.1
Origin: https://sklep.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

ينبغي أن يرد الخادم بإذن يشمل الأصل وأسلوب الطلب والترويسات المطلوبة:

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type

لا يرسل المتصفح طلب POST الفعلي إلا بعد نجاح الفحص. ويجب أن تتضمن الاستجابة لطلب POST هذا أيضًا ترويسة Access-Control-Allow-Origin المناسبة.

لا يحتاج كل طلب POST إلى فحص تمهيدي. فالترويسات ونوع المحتوى المُرسل لهما دور أيضًا؛ وغالبًا لا يحتاج طلب GET عادي من دون ترويسات إضافية إلى هذا الفحص.

ملفات تعريف الارتباط والنجمة: محذوران مهمان

يمكن لواجهة API عامة، يُراد إتاحة استجاباتها للبرامج النصية من أي أصل، أن تعيد:

Access-Control-Allow-Origin: *

لكن النجمة ليست حلًا مناسبًا للطلبات التي تُرسل مع ملفات تعريف الارتباط في وضع تضمين بيانات الاعتماد. في هذه الحالة، قد تستخدم الشفرة credentials: 'include'، ويجب على الخادم تحديد أصل مسموح به بعينه وإعادة ما يلي:

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Credentials: true

لا يضمن إعداد CORS وحده إرسال ملفات تعريف الارتباط. إذ تظل قواعد هذه الملفات، ومنها SameSite، وقيود الخصوصية في المتصفح سارية أيضًا.

إذا كان الخادم يدعم عدة أصول موثوقة، فعليه التحقق من ترويسة Origin بمقارنتها بقائمة القيم المسموح بها. فإعادة أي أصل وارد دون تحقق، مع السماح ببيانات الاعتماد، قد تكشف استجابات خاصة. وعندما تعتمد الاستجابة على Origin، ينبغي أيضًا تضمين Vary: Origin حتى تميّز الذاكرة المؤقتة بين نسخ الاستجابة المختلفة.

كيفية تشخيص خطأ CORS

افتح تبويب الشبكة في أدوات المطوّرين بالمتصفح، ثم أجرِ عمليات التحقق الأربع التالية:

  1. قارن البروتوكول واسم المضيف والمنفذ لكل من الصفحة وواجهة API.
  2. تحقق مما إذا كان هناك طلب OPTIONS، وما إذا كان الخادم يرد عليه على نحو صحيح.
  3. افحص ترويسات الاستجابة للطلب الفعلي، حتى عندما يعيد الخادم خطأً.
  4. تحقق من أن الإذن يشمل أسلوب الطلب والترويسات المستخدمة وبيانات الاعتماد إن وُجدت.

نجاح الطلب في curl لا يثبت صحة إعداد CORS، لأن هذه الأداة لا تفرض قيود المتصفح. كما أن ضبط mode: 'no-cors' لن يتيح قراءة JSON؛ فهو ينتج استجابة معتمة لا يمكن للبرنامج النصي الوصول إلى محتواها.

لا يحل CORS محل تسجيل الدخول أو التحكم في الصلاحيات أو الحماية من CSRF، أي إجبار المستخدم على تنفيذ إجراءات غير مرغوبة عبر موقع آخر. وقد تصل بعض الطلبات إلى الخادم رغم حظر قراءة الاستجابة. ابدأ الإصلاح بإعداد واجهة API أو وسيط تتحكم فيه، لا بتعطيل وسائل الحماية في المتصفح.

اقرأ أيضًا

تعرّف على كيفية معالجة PHP-FPM لطلبات PHP، والفرق بينه وبين خادم الويب، وكيفية ضبط عملياته وفق حركة الزيارات وموارد الاستضافة.
تعرّف إلى BIND، برنامج خادم DNS مفتوح المصدر، وأدواره في خدمة النطاقات، ومتطلبات تأمينه، ومتى تختار تشغيله بنفسك أو خدمة مُدارة.
تعرّف إلى سجل REGON ورقم التعريف فيه في بولندا، والفرق بينه وبين NIP وKRS، وكيفية التحقق من بيانات المنشآت وحدود ما يثبته السجل.