التعامل مع الـ CORS ومشاكل الاتصال بين النطاقات


يا هلا والله بالجميع! اليوم بنتكلم عن واحد من أكثر المشاكل اللي تواجهنا كـ Developers، خصوصاً لما نشتغل على تطبيقات فيها Frontend و Backend منفصلين: CORS ومشاكل الاتصال بين النطاقات المختلفة.

وش سالفة الـ CORS؟

ببساطة، المتصفحات عندها سياسة أمان اسمها Same-Origin Policy. هالسياسة تقول لك: "يا حبيبي، موقعك اللي على example.com ما يقدر يطلب بيانات من api.anothersite.com إلا إذا سمح له الموقع الثاني صراحةً". الهدف من هالسياسة هو حمايتك من مواقع خبيثة تحاول تسرق بياناتك أو تسوي أشياء ما تبغاها. هنا يجي دور الـ CORS (Cross-Origin Resource Sharing) اللي هي طريقة آمنة عشان تسمح للمواقع تتواصل مع بعضها.

متى تضرب معاك مشكلة الـ CORS؟

تضرب معاك المشكلة لما يكون عندك:

  • نطاق مختلف (مثلاً: frontend.com يكلم backend.com)
  • منفذ مختلف (مثلاً: localhost:3000 يكلم localhost:5000)
  • بروتوكول مختلف (مثلاً: http يكلم https)

بأختصار، أي طلب يطلع من متصفحك لعنوان مختلف عن اللي أنت فيه حالياً، بيحاول المتصفح يطبق عليه سياسة الـ Same-Origin Policy وبيشوف هل فيه تصريح CORS ولا لا.

كيف يشتغل الـ CORS (على خفيف)؟

لما متصفحك يرسل طلب لـ API على نطاق مختلف، يضيف هيدر اسمه Origin يقول فيه: "يا API، أنا جاي من https://myfrontend.com". الـ API هنا لازم يرد بهيدر اسمه Access-Control-Allow-Origin يقول فيه: "يا myfrontend.com، أنا أسمح لك تتواصل معي".

ملاحظة مهمة: مشكلة الـ CORS هي مشكلة من جهة المتصفح (Browser-side). يعني لو سويت نفس الطلب هذا عن طريق curl أو Postman، ماراح تشوف مشكلة CORS أبدًا لأنهم ما يطبقون سياسة Same-Origin Policy.

طلبات الـ Preflight (فحص ما قبل الإرسال)

أحياناً، المتصفح يكون "حذر" شوي. قبل ما يرسل الطلب الفعلي (مثلاً POST أو PUT مع هيدرات معينة)، يرسل طلب اسمه OPTIONS أول شيء. هذا الطلب يسمونه Preflight Request. يسأل فيه الـ API: "يا API، هل تسمح لي أرسل لك طلب POST من myfrontend.com ومع الهيدرات الفلانية؟". إذا الـ API رد بإيجاب (عن طريق هيدرات CORS مناسبة)، بعدها المتصفح يرسل الطلب الفعلي. وإذا ما رد، المتصفح يوقف الطلب ويعطيك إيرور CORS.

الحلول (من جهة الـ Backend هي الأفضل)

1. ضبط الـ Access-Control-Allow-Origin

هذا هو أهم هيدر. لازم الـ Backend يرجعه في كل استجابة. ممكن تحط فيه نطاق معين، أو نجمة * عشان تسمح لأي نطاق يتواصل معك (وهذا مو دايم أفضل حل أمنيًا في بيئة الإنتاج).

مثال في Node.js (Express):

const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://myfrontend.com'); // أو '*' للسماح للكل
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true'); // لو بتستخدم كوكيز أو Authentication Headers

  // التعامل مع طلبات الـ Preflight
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  next();
});

// باقي الـ routes حقك
app.get('/data', (req, res) => {
  res.json({ message: 'Hello from API!' });
});

app.listen(5000, () => {
  console.log('Backend running on port 5000');
});

مثال في Python (Flask):

from flask import Flask, jsonify, request
from flask_cors import CORS # مكتبة جاهزة تسهل عليك الموضوع

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "https://myfrontend.com"}}) # أو "*" للسماح للكل

@app.route('/data')
def get_data():
    return jsonify({'message': 'Hello from API!'})

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

2. ضبط الـ Access-Control-Allow-Methods

تحدد فيها أنواع الطلبات (GET, POST, PUT, DELETE, OPTIONS) اللي تسمح فيها من النطاقات الأخرى.

3. ضبط الـ Access-Control-Allow-Headers

تحدد فيها الهيدرات المخصصة اللي تسمح للمتصفح يرسلها مع الطلب (مثل Authorization, Content-Type).

4. ضبط الـ Access-Control-Allow-Credentials

إذا كنت تستخدم الكوكيز أو Authorization هيدر عشان تسوي Authentication، لازم تحط هذا الهيدر بـ true. ملاحظة: ما تقدر تستخدم * مع Access-Control-Allow-Credentials: true، لازم تحدد نطاق معين.

حلول أخرى (أقل مثالية)

1. استخدام Proxy (الوكيل)

هذي طريقة ممتازة لو ما تقدر تعدل على الـ Backend. تسوي سيرفر وسيط (Proxy Server) على نفس النطاق اللي فيه الـ Frontend حقك. الـ Frontend يطلب من الـ Proxy، والـ Proxy بدوره يطلب من الـ API الحقيقي. بما إن الطلب بين الـ Frontend والـ Proxy على نفس النطاق، فما فيه مشكلة CORS. والطلب بين الـ Proxy والـ API يكون طلب من السيرفر للسيرفر، وهذا ما يخضع لسياسة Same-Origin Policy.

مثال في Node.js (باستخدام http-proxy-middleware):

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use('/api', createProxyMiddleware({ 
  target: 'http://localhost:5000', // عنوان الـ API الحقيقي
  changeOrigin: true, // مهم عشان الـ API يستقبل الطلب كأنه جاي من الـ Proxy
  pathRewrite: {
    '^/api': '', // لو تبغى تحذف '/api' من مسار الطلب قبل إرساله للـ API
  },
}));

// باقي الـ Frontend هنا، أو ملفات الـ Static
app.use(express.static('public')); 

app.listen(3000, () => {
  console.log('Frontend/Proxy running on port 3000');
});

2. JSONP (قديمة ونادرًا تستخدم الآن)

هذي طريقة قديمة تعتمد على إدراج سكريبتات ديناميكية عشان تتجاوز مشكلة الـ CORS. لكنها تعتبر أقل أمانًا ولها قيودها، ونادرًا ما تستخدم في المشاريع الحديثة. ما بتعمق فيها كثير.

نصائح للـ Debugging

  • افتح الـ Developer Tools في متصفحك (F12).
  • روح لتبويبة Network. شوف الطلبات اللي فشلت. عادةً بتشوف خطأ CORS policy واضح في الـ Console أو في تفاصيل الطلب.
  • شوف هيدرات الطلب (Request Headers) وهيدرات الاستجابة (Response Headers). تأكد إن Access-Control-Allow-Origin موجود وصحيح في استجابة الـ API.
  • إذا كان الطلب OPTIONS يفشل، فالمشكلة في الـ Preflight Request. تأكد إن الـ Backend يتعامل معها صح.

أتمنى إن هالدرس المختصر كان مفيد ويحل لكم مشاكل الـ CORS اللي تمرون فيها. بالتوفيق يا أبطال!