التعامل مع CORS بشكل صحيح في Django وFastAPI

التعامل مع CORS بشكل صحيح في Django وFastAPI (بدون تعريض مشروعك للخطر)

المشكلة: كثير من المطورين عندما يواجهون خطأ CORS في المتصفح يكتبون أول حل يجدونه في StackOverflow: السماح للجميع *، فتح كل الهيدرز، وكل الميثودز… ثم ينسون الموضوع.

هذا الأسلوب قد يحل الخطأ أمامك، لكنه غالبًا يفتح بابًا لمشاكل أمنية خطيرة على مشروعك، خاصة إذا كنت تعمل على API عام أو لوحة تحكم أو مشروع به بيانات حساسة. في هذا المقال سنشرح:

  • ما هي مشكلة CORS أصلًا؟ ولماذا تظهر؟
  • كيف تعمل CORS في المتصفح من الناحية التقنية؟
  • كيفية إعداد CORS بشكل صحيح في Django وFastAPI.
  • أخطاء شائعة تجعل CORS نقطة ضعف في مشروعك.
  • نصائح عملية لوضع إعدادات CORS آمنة وقابلة للتوسع.

هذا الشرح موجه للمطورين الذين سبق وعملوا مع Django أو FastAPI أو REST APIs، ويريدون فهم جذور مشكلة CORS Django FastAPI بدلًا من حلها عشوائيًا.

ما هي CORS؟ ولماذا تظهر المشكلة أصلًا؟

CORS اختصار لـ Cross-Origin Resource Sharing، وهي آلية في المتصفح تهدف لحماية المستخدم من أن يقوم موقع معين بطلب بيانات حساسة من موقع آخر بدون إذن.

المتصفح يعرّف origin كالتالي:

  • البروتوكول: http أو https
  • اسم الدومين: example.com
  • المنفذ (port): 80, 443, 8000 …

إذا كان عندك:

  • الـ frontend على: http://localhost:3000
  • والـ backend على: http://localhost:8000

فهما origin مختلفان، حتى لو كانا على نفس الجهاز. عندها يبدأ المتصفح في تطبيق قواعد CORS.

طريقة عمل CORS بشكل مبسط

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

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials

بناءً على هذه الهيدرز، إما يسمح المتصفح بتمرير الرد للـ JavaScript، أو يمنعه ويظهر لك الخطأ الشهير في الـ Console.

Preflight Request (خيال الظل قبل الطلب الحقيقي)

بعض الطلبات تعتبرها CORS "خطيرة" أو "معقدة" (مثل POST مع هيدرز مخصصة). في هذه الحالة المتصفح يرسل طلب OPTIONS أولًا يسمى Preflight:

  1. يرسل المتصفح OPTIONS إلى الـ API، مع هيدرز توضح نوع الطلب الحقيقي القادم.
  2. الخادم (Django أو FastAPI) يجب أن يرد بهيدرز CORS الصحيحة.
  3. إذا كان الرد مناسبًا، بعدها المتصفح يرسل الطلب الحقيقي (GET/POST/PUT…)

إذا تجاهلت أو منعت طلبات OPTIONS، ستجد نفسك أمام أخطاء CORS حتى لو أضفت Allow-Origin صحيح.

لماذا حل CORS بشكل عشوائي خطير؟

الأغلب يقوم بالتالي:

  • يسمح لـ Access-Control-Allow-Origin: *
  • يسمح بـ Access-Control-Allow-Credentials: true في نفس الوقت
  • يسمح بجميع الميثودز والHeaders

هذا الخليط قد يؤدي إلى:

  • هجمات CSRF من مواقع خارجية إذا كنت تعتمد على Cookies للجلسات.
  • تسرّب بيانات لمواقع ليست تحت سيطرتك، خاصة لو API عام.
  • صعوبة التحكم مستقبلاً عند إضافة دومينات جديدة أو بيئات staging/production.

تذكر أن CORS ليست نظام صلاحيات معقد، لكنها طبقة حماية في المتصفح، وعندما تفتحها بالكامل فأنت فعليًا تقول: أي موقع في العالم يمكنه استغلال الـ API كما لو كان من نفس الـ frontend الخاص بك.

إذا أردت فهم كيف تتكامل CORS مع مفاهيم أخرى مثل Middleware في Django، يمكنك الاطلاع على مقالنا: شرح طريقة استخدام Middleware في Django مع أمثلة وأفضل الممارسات.

إعداد CORS بشكل صحيح في Django

في Django لا توجد CORS بشكل افتراضي، غالبًا ستستخدم مكتبة: django-cors-headers.

الخطوة 1: التثبيت والإعداد الأولي

قم بتثبيت المكتبة:

pip install django-cors-headers

ثم أضفها إلى INSTALLED_APPS في settings.py:

'corsheaders',

وأضف الـ Middleware في أعلى قائمة MIDDLEWARE (مهم أن تسبق CommonMiddleware):

'corsheaders.middleware.CorsMiddleware',

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

الخطوة 2: إعداد CORS بشكل آمن

بدلًا من السماح للجميع، استخدم قائمة محددة من الـ origins الموثوقة.

أمثلة إعداد آمن أساسي:

في مرحلة التطوير (Development):

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]

CORS_ALLOW_CREDENTIALS = True

بهذا تقول لـ Django: اسمح فقط للـ frontend على هذه العناوين بالوصول مع دعم الـ Cookies أو الـ Authorization header.

في مرحلة الإنتاج (Production):

CORS_ALLOWED_ORIGINS = [
    "https://frontend.example.com",
    "https://admin.example.com",
]

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = [
    "GET",
    "POST",
    "PUT",
    "PATCH",
    "DELETE",
    "OPTIONS",
]

CORS_ALLOW_HEADERS = [
    "content-type",
    "authorization",
    "x-requested-with",
]

ملاحظات مهمة:

  • لا تستخدم CORS_ALLOW_ALL_ORIGINS = True في الإنتاج إن كان عندك Sessions / Cookies.
  • عند استخدام CORS_ALLOW_CREDENTIALS = True، لا يمكنك استخدام * مع Access-Control-Allow-Origin.
  • حدد الميثودز والهيدرز حسب ما يحتاجه الـ API فقط.

أخطاء شائعة مع CORS في Django

  • نسيان إضافة Middleware في الأعلى:
    إذا لم تضف corsheaders.middleware.CorsMiddleware في بداية القائمة، قد لا تعمل CORS كما تتوقع، لأن Middlewares أخرى قد تعدل على الرد قبله.
  • الاعتماد على CORS فقط في حماية API:
    تذكر أن CORS تعمل على مستوى المتصفح فقط، لكنها لا تمنع أي أحد من استدعاء API من طرف آخر (مثل script خارجي أو Postman)، لذلك لا تستخدمها كطبقة حماية وحيدة.
  • خلط CORS مع CSRF:
    CORS ≠ CSRF. إذا كنت تعتمد على Cookies للجلسات في Django، عليك فهم CSRF بشكل صحيح أيضًا، لأن السماح بالـ credentials مع origins متعددة قد يفتح ثغرات.

إعداد CORS في FastAPI بشكل صحيح

في FastAPI، إعداد CORS يتم غالبًا باستخدام CORSMiddleware الجاهز من Starlette.

الخطوة 1: إضافة CORSMiddleware

مثال أساسي:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
    "https://frontend.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type", "X-Requested-With"],
)

هنا تحدد بوضوح:

  • أي origins مسموح لها.
  • هل نسمح بإرسال الـ Cookies/Authorization headers (allow_credentials).
  • ما هي الميثودز والهيدرز المسموح بها.

أوضاع Development vs Production في FastAPI

في التطوير، قد تسمح بـ allow_origins=["*"] مع allow_credentials=False فقط لتسريع العمل، لكن لا تنقل هذا الإعداد للإنتاج.

مثال إعداد للمراحل الأولى:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
)

ثم في الإنتاج، استبدله بإعداد صارم مثل المثال السابق.

أخطاء شائعة في FastAPI مع CORS

  • استخدام allow_origins=["*"] مع allow_credentials=True:
    حسب المواصفات، هذا غير مسموح، وقد يؤدي إلى سلوك غير متوقع في بعض المتصفحات.
  • نسيان OPTIONS في allow_methods:
    إذا كنت ترسل طلبات POST مع هيدرز مخصصة من المتصفح، ولم تسمح بـ OPTIONS، قد تفشل طلبات preflight.
  • تجاهل بيئة الإنتاج:
    يظل المشروع يعمل على إعدادات "مفتوحة" منذ بيئة التطوير، خاصة إن لم يكن لديك ملفات إعدادات منفصلة لكل بيئة.

إذا كان مشروعك يستخدم WebSockets في FastAPI، فلاحظ أن CORS لا تُطبّق بنفس الطريقة على WebSockets، لكنك ما زلت تحتاج لتحديد من يستطيع الاتصال بالسيرفر. يمكنك مراجعة: التعامل مع WebSockets في FastAPI: تطبيق عملي.

أفضل الممارسات لإعداد CORS في مشاريع Django وFastAPI

1. فصل الإعدادات حسب البيئة

من الأخطاء الشائعة أن يكون ملف settings.py في Django أو إعدادات FastAPI موحّدة لكل شيء. الأفضل:

  • development settings: تسمح بـ localhost وما يشبهه.
  • production settings: تسمح فقط بالدومينات الرسمية للمشروع.

يمكنك مثلًا استخدام متغيرات بيئة (Environment Variables) لتحديد CORS_ALLOWED_ORIGINS أو origins في FastAPI.

2. لا تفتح CORS أكثر من اللازم

  • حدّد origins معروفة فقط.
  • لا تستخدم "*" في الإنتاج، خاصة مع allow_credentials.
  • اسمح بالميثودز والهيدرز التي تحتاجها فعليًا فقط.

3. افهم نموذج المصادقة الذي تستخدمه

إذا كنت تستخدم:

  • Cookies وجلسات (Session-based Auth) كما في Django الافتراضي: كن حذرًا جدًا مع origins المتعددة، وفكر في CSRF بجانب CORS.
  • JWT / OAuth2 عبر Authorization Header كما في كثير من مشاريع FastAPI: CORS ستكون أبسط، لكن لا تزال تحتاج لتحديد origins المسموح لها.

للمزيد حول أنظمة المصادقة في Django وFastAPI، يمكنك الاطلاع على: تنفيذ OAuth2 في Django و FastAPI: دليل عملي كامل.

4. راقب لوج الأخطاء بدل "إطفاء" CORS

إذا ظهر خطأ CORS:

  1. اقرأ رسالة الخطأ كاملة في الـ Console.
  2. استخدم أدوات المتصفح (Network tab) لفحص طلب preflight (OPTIONS) والردود.
  3. قارن بين origin الفعلي و CORS_ALLOWED_ORIGINS/allow_origins في الكود.

بهذا الأسلوب ستفهم أين المشكلة بالضبط بدلاً من تعطيل CORS بالكامل.

أمثلة عملية: سيناريوهات شائعة

سيناريو 1: React + Django REST Framework

  • Frontend على: http://localhost:3000
  • Backend على: http://localhost:8000

الإعداد المقترح في settings.py:

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
]

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = [
    "GET",
    "POST",
    "PUT",
    "PATCH",
    "DELETE",
    "OPTIONS",
]

CORS_ALLOW_HEADERS = [
    "content-type",
    "authorization",
    "x-requested-with",
]

في React، إذا كنت تستخدم Cookies للجلسات، تأكد من إضافة:

axios.defaults.withCredentials = true;

سيناريو 2: SPA على دومين مستقل + FastAPI API

  • Frontend: https://app.example.com
  • API: https://api.example.com

إعداد FastAPI:

origins = [
    "https://app.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,  # إذا كنت تستخدم JWT في Authorization Header فهذه ليست ضرورية عادة
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type"],
)

إذا كنت لا تستخدم Cookies، يمكنك حتى جعل allow_credentials=False لتبسيط الأمور.

خلاصة: تعامل مع CORS كجزء من تصميم الـ API، لا كإزعاج مؤقت

مشكلة CORS Django FastAPI ليست "بج" في المتصفح، بل هي طبقة حماية مهمة لحماية المستخدمين من استغلال APIs من مواقع غير موثوقة.

بدل أن:

  • تفتح كل شيء بـ *
  • تسمح بكل الميثودز والهيدرز بدون تفكير

افعل الآتي:

  1. حدّد بشكل واضح ما هي الـ origins المسموح لها.
  2. اضبط إعدادات CORS بشكل منفصل لكل بيئة (تطوير، اختبار، إنتاج).
  3. افهم علاقة CORS بنموذج المصادقة الذي تستخدمه (Sessions, JWT, OAuth2).
  4. استخدم Middlewares (في Django وFastAPI) لتطبيق CORS بشكل منظم وقابل للصيانة.

كل ما زاد وعيك بهذه التفاصيل، زادت جودة وأمان الـ APIs التي تبنيها، وقلّت "الحلول السحرية" التي قد تحل خطأ واحدًا في الـ Console لكنها تفتح أبوابًا كاملة للهجمات على مشروعك.

حول المحتوى:

شرح جذور مشكلة CORS، كيفية إعدادها بشكل آمن، الأخطاء الشائعة، ولماذا حله بشكل عشوائي يعرض مشروعك للخطر.

هل كان هذا مفيدًا لك؟

أضف تعليقك