كيفية إصلاح أخطاء CORS: تصحيح Access-Control-Allow-Origin

هل تواجه خطأ CORS؟ تعرف على ما الذي يسببه، وكيف يعمل الطلب المسبق، وأكثر 6 إخفاقات شيوعًا في Access-Control-Allow-Origin، والحل الدقيق لكل منها.

Ashley Innocent

Ashley Innocent

31 أغسطس 2026

كيفية إصلاح أخطاء CORS: تصحيح Access-Control-Allow-Origin

Apidog للمؤسسات

النشر على الخوادم المحلية

SSO و RBAC

متوافق مع SOC 2

استكشف Apidog للمؤسسات

تقوم بنشر واجهة أمامية جديدة، تفتح وحدة التحكم (console)، وتجدها هناك: خطأ CORS أحمر يخبرك بأن الطلب "تم حظره بواسطة سياسة CORS". تعمل واجهة برمجة التطبيقات (API) الخاصة بك بشكل جيد في Apidog أو curl، ومع ذلك يرفض المتصفح تسليم الاستجابة إلى JavaScript الخاص بك. محبط؟ نعم. غامض؟ ليس بمجرد أن تعرف أين يكمن الخطأ.

إليك الحقيقة الأساسية التي تخفيها معظم الدروس التعليمية: خطأ CORS يتم فرضه بواسطة المتصفح ولكنه يحدث بسبب الخادم. يحظر المتصفح الاستجابة لأن الخادم الخاص بك لم يرسل رؤوس Access-Control-Allow-Origin الصحيحة. لذلك فإن الإصلاح دائمًا ما يحدث تقريبًا في تكوين الخادم، وليس في كود الواجهة الأمامية الخاص بك.

يرشدك هذا الدليل إلى ما يفعله CORS، وكيف يعمل طلب الفحص المسبق (preflight request)، ورسائل خطأ CORS الستة الأكثر شيوعًا مع الإصلاح الدقيق لكل منها، والتكوينات العاملة لـ Express و Spring Boot و Nginx. سترى أيضًا كيفية تصحيح الأخطاء من خارج المتصفح، وهي أسرع طريقة للتمييز بين "تكوين الخادم خاطئ" و "المتصفح حظر الطلب".

ما هو خطأ CORS (وما ليس كذلك)

CORS هو اختصار لـ Cross-Origin Resource Sharing (مشاركة الموارد عبر المصادر). بشكل افتراضي، تفرض المتصفحات سياسة نفس المصدر (same-origin policy): لا يمكن لـ JavaScript الذي يعمل على https://app.example.com قراءة الاستجابات من https://api.example.com، لأن المخطط (scheme) أو المضيف (host) أو المنفذ (port) يختلف. CORS هي الآلية التي تستخدمها الخوادم لتخفيف هذه القاعدة عن قصد. تتوفر التفاصيل الكاملة في وثائق MDN CORS، ويتم تعريف الخوارزمية الأساسية في مواصفات Fetch.

ثلاث نقاط توضح معظم الارتباك:

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

تشريح طلب الفحص المسبق (Preflight Request)

قبل بعض الطلبات عبر المصادر، يرسل المتصفح طلبًا استكشافيًا: طلب OPTIONS يسمى "الفحص المسبق" (preflight). يتم إطلاقه عندما يستخدم طلبك طرقًا تتجاوز GET أو HEAD أو POST، أو يرسل رؤوسًا مخصصة مثل Authorization، أو يستخدم Content-Type مثل application/json.

يبدو طلب الفحص المسبق كما يلي:

OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

يسأل المتصفح: "صفحة على app.example.com تريد إجراء طلب POST هنا بهذه الرؤوس. هل هذا مسموح؟" إجابة الخادم الصحيحة:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin

إذا كان أي جزء مفقودًا، يلغي المتصفح الطلب الفعلي قبل أن يتم إطلاقه. لا يتم تشغيل نقطة نهاية API الخاصة بك أبدًا، ولا تظهر سجلاتك شيئًا سوى وصول طلب OPTIONS، وتعرض وحدة التحكم خطأ CORS. يخبر Access-Control-Max-Age المتصفح بتخزين هذا الحكم مؤقتًا (86400 ثانية هنا)، لذا تتجاوز الطلبات المتكررة الفحص المسبق.

ضع هذه الرقصة ذات الخطوتين في الاعتبار. نصف عملية تصحيح أخطاء CORS بأكملها تتلخص في سؤال واحد: هل فشل الفحص المسبق، أم فشل الطلب الفعلي؟

أكثر 6 أخطاء CORS شيوعًا وكيفية إصلاح كل منها

تكتب المتصفحات رسائل خطأ CORS دقيقة بشكل مدهش. طابق خطأك مع القائمة أدناه.

1. لا يوجد رأس 'Access-Control-Allow-Origin'

الحالة الكلاسيكية. أرسل الخادم الخاص بك استجابة بدون أي رؤوس CORS على الإطلاق. لم يكن لدى المتصفح ما يقيمه، لذلك حظر الوصول.

الإصلاح: قم بتكوين الخادم لإرسال Access-Control-Allow-Origin إما مع المصدر الطالب المحدد أو * لواجهات برمجة التطبيقات العامة الخالية من بيانات الاعتماد:

Access-Control-Allow-Origin: https://app.example.com

فخ واحد: غالبًا ما تتجاهل استجابات الأخطاء رؤوس CORS حتى عندما تتضمنها استجابات النجاح. إذا كانت واجهة برمجة التطبيقات الخاصة بك ترجع رمز 500 وقامت Middleware فقط بتزيين الرمز 200، فستعرض وحدة التحكم خطأ CORS بدلاً من خطأ الخادم الحقيقي. تأكد من إرفاق رؤوس CORS بكل استجابة، بما في ذلك صفحات 403 Forbidden و 500.

2. لا يمكن استخدام الرمز العام '*' مع بيانات الاعتماد

تقول الرسالة: "يجب ألا تكون قيمة رأس 'Access-Control-Allow-Origin' الرمز العام '*' عندما يكون وضع بيانات الاعتماد للطلب هو 'include'."

ترسل الواجهة الأمامية الخاصة بك ملفات تعريف الارتباط أو رؤوس المصادقة مع credentials: 'include'، ولكن الخادم يجيب بـ Access-Control-Allow-Origin: *. مواصفات Fetch تحظر هذا الاقتران؛ فالرمز العام بالإضافة إلى بيانات الاعتماد سيسمح لأي موقع على الإنترنت بقراءة الاستجابات المصادق عليها.

الإصلاح: أعد المصدر الدقيق بدلاً من الرمز العام، وأضف رأس بيانات الاعتماد:

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

تحقق من Origin الوارد مقابل قائمة المصادر المسموح بها قبل إعادته. عكس المصادر العشوائية مع تمكين بيانات الاعتماد يهزم الحماية بأكملها.

3. استجابة طلب الفحص المسبق لا تجتاز فحص التحكم في الوصول

لم يتعامل الخادم الخاص بك أبدًا مع طلب OPTIONS. ربما يحدد المسار (route) فقط POST، لذا يعيد OPTIONS رمز 404 أو 405. ربما رفضه Middleware للمصادقة برمز 401 لأن الفحص المسبق لا يحمل أي رمز مميز (browsers never attach credentials to preflights).

الإصلاح: تعامل مع OPTIONS بشكل صريح وأرجع رمز 2xx مع المجموعة الكاملة من رؤوس CORS قبل تشغيل المصادقة. في معظم الأطر، يؤدي تركيب Middleware الخاص بـ CORS أولاً إلى حل المشكلة. إذا كنت تكتبه يدويًا:

app.options('/v1/orders', (req, res) => {
  res.set({
    'Access-Control-Allow-Origin': 'https://app.example.com',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Authorization, Content-Type'
  });
  res.sendStatus(204);
});

4. قيمة الرأس لا تتطابق مع المصدر المقدم

يرسل الخادم رأس Access-Control-Allow-Origin، لكنه يحدد مصدرًا خاطئًا. الأسباب الشائعة: مصدر إنتاجي مبرمج بشكل ثابت بينما تختبر من http://localhost:5173، أو فشل مقارنة قائمة المصادر المسموح بها (allowlist) بين http و https، أو شرطة مائلة زائدة (trailing slash) (https://app.example.com/ ليست قيمة مصدر صالحة).

الإصلاح: قارن رأس Origin الخاص بالطلب بقائمة المصادر المسموح بها الخاصة بك بدقة، وأعد المصدر المطابق، وأرسل Vary: Origin حتى لا تقدم مخابئ التخزين وشبكات CDN رأس مصدر واحد لآخر:

const allowed = ['https://app.example.com', 'http://localhost:5173'];
if (allowed.includes(req.headers.origin)) {
  res.set('Access-Control-Allow-Origin', req.headers.origin);
  res.set('Vary', 'Origin');
}

5. حقل رأس الطلب أو الطريقة غير مسموح بها

رسالتان متشابهتان: "حقل رأس الطلب authorization غير مسموح به بواسطة Access-Control-Allow-Headers في استجابة الفحص المسبق" و "الطريقة PUT غير مسموح بها بواسطة Access-Control-Allow-Methods."

نجح الفحص المسبق، لكن إجابته لم تغط ما يحتاجه طلبك. لقد أضفت رأس Authorization أو X-Request-Id، ولم تذكره قائمة المصادر المسموح بها (allowlist) في الخادم أبدًا.

الإصلاح: قم بتوسيع استجابة الفحص المسبق لتشمل كل رأس وطريقة ترسلها الواجهة الأمامية الخاصة بك:

Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id

أسماء الرؤوس هنا غير حساسة لحالة الأحرف. الطرق حساسة لحالة الأحرف وتكون بأحرف كبيرة.

6. إعادة التوجيه غير مسموح بها لطلب الفحص المسبق

وصل الفحص المسبق إلى عنوان URL يرجع رمز 301 أو 302، وترفض المتصفحات متابعة عمليات إعادة التوجيه أثناء الفحص المسبق. الجناة النموذجيون: عنوان URL يبدأ بـ http يعيد التوجيه إلى https، أو شرطة مائلة زائدة مفقودة يقوم إطار عملك بإعادة توجيهها "بمساعدة"، أو بوابة تقوم بإعادة توجيه /v1/orders إلى /v1/orders/.

الإصلاح: وجه الواجهة الأمامية الخاصة بك إلى عنوان URL النهائي مباشرة. استخدم https من البداية، طابق اتفاقية الشرطة المائلة الزائدة (trailing-slash) الخاصة بالموجه الخاص بك، وتأكد من خلال استدعاء OPTIONS يدويًا للتحقق مما إذا كانت نقطة النهاية تستجيب برمز 2xx بدلاً من 3xx.

أمثلة تكوين الخادم

إليك إعداد CORS الصحيح في ثلاث بيئات شائعة.

Express

استخدم وسيط CORS (middleware) الرسمي بدلاً من كتابة الرؤوس يدويًا:

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

app.use(cors({
  origin: ['https://app.example.com', 'http://localhost:5173'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  credentials: true,
  maxAge: 86400
}));

قم بتركيبه قبل وسيط المصادقة (auth middleware) الخاص بك حتى لا يتم رفض الفحوصات المسبقة بسبب الرموز المفقودة. يحصل مطورو Python على نفس النمط من امتداد Flask-CORS، الذي يغلف منطق الرأس المتطابق لتطبيقات Flask.

Spring Boot

التكوين العام عبر WebMvcConfigurer:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/v1/**")
            .allowedOrigins("https://app.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Authorization", "Content-Type")
            .allowCredentials(true)
            .maxAge(86400);
    }
}

هل تستخدم Spring Security؟ قم باستدعاء .cors(Customizer.withDefaults()) في سلسلة فلتر الأمان (security filter chain) أيضًا، وإلا فإن طبقة الأمان ستحظر الفحوصات المسبقة قبل أن تراها إعدادات MVC. راجع وثائق Spring CORS لمجموعة الخيارات الكاملة.

Nginx

عندما ينهي Nginx الطلبات أمام تطبيقك، أجب على الفحوصات المسبقة عند الحافة (edge):

location /v1/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
        add_header Access-Control-Max-Age 86400 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Vary "Origin" always;
    proxy_pass http://backend;
}

العلامة always مهمة. بدونها، يسقط Nginx توجيهات add_header على استجابات 4xx و 5xx، مما يعيد إنشاء الخطأ رقم واحد في كل طلب فاشل. واختر طبقة واحدة لتتولى CORS: إذا أضاف كل من Nginx وتطبيقك الرؤوس، فسترى المتصفحات تكرارات مثل Access-Control-Allow-Origin: *, * وترفض الاستجابة.

تصحيح أخطاء CORS خارج المتصفح باستخدام Apidog

يخبرك خطأ وحدة التحكم بأن المتصفح حظر شيئًا ما. لكنه لا يخبرك بما أرسله الخادم. أسرع طريقة لمعرفة الحقيقة هي إخراج المتصفح من المعادلة.

Apidog هو عميل API لسطح المكتب، لذا فإن طلباته لا تخضع لفحوصات CORS الخاصة بالمتصفح على الإطلاق. وهذا يمنحك تجربة نظيفة: أرسل نفس الطلب من Apidog الذي كانت الواجهة الأمامية الخاصة بك ترسله. إذا نجح هناك، فإن منطق API الخاص بك سليم والمشكلة هي مجرد رؤوس CORS مفقودة. إذا فشل هناك أيضًا، فلديك خطأ عادي في API متنكّر في زي CORS، وتنطبق تقنيات اختبار API العامة.

تبدو جلسة تصحيح أخطاء CORS في Apidog كما يلي:

  1. إعادة تشغيل الطلب الحقيقي. انسخ الطلب الفاشل من علامة تبويب الشبكة (Network tab) في متصفحك وأعد إنشائه في Apidog بنفس الطريقة والرؤوس والجسم (body). تحقق من الحالة والجسم. رمز 500 هنا يعني أن CORS لم يكن مشكلتك أبدًا.
  2. اختبار الفحص المسبق يدويًا. قم بإنشاء طلب جديد، عيّن الطريقة إلى OPTIONS، وأضف الرؤوس التي يرسلها المتصفح: Origin: https://app.example.com، Access-Control-Request-Method: POST، و Access-Control-Request-Headers: authorization, content-type. أرسله.
  3. فحص رؤوس الاستجابة. في لوحة الاستجابة (response pane)، ابحث عن Access-Control-Allow-Origin، Access-Control-Allow-Methods، و Access-Control-Allow-Headers. قارن كل قيمة بما تحتاجه واجهتك الأمامية. سيظهر رأس مفقود، أو مصدر خاطئ، أو حالة 3xx على الفور، دون الحاجة إلى تخمين من وحدة التحكم.
  4. التحقق من الإصلاح. بعد تغيير تكوين الخادم، أعد إرسال طلب OPTIONS المحفوظ نفسه وشاهد تحديث الرؤوس. لا يوجد إعادة نشر للواجهات الأمامية، ولا طقوس مسح ذاكرة التخزين المؤقت.

تُحل سير العمل هذا أيضًا النقاش الأبدي "يعمل في عميل API الخاص بي، ويفشل في المتصفح" في ثوانٍ، وهو نفس اللغز وراء سؤال اختبار Postman CORS. يعمل العميل لأنه يتجاوز CORS. يفشل المتصفح لأن الخادم الخاص بك لم يقل الكلمات السحرية. قم بتنزيل Apidog مجانًا واحتفظ بطلب OPTIONS محفوظًا بجوار اختبارات نقطة النهاية العادية الخاصة بك؛ سيتم إخماد حرائق CORS المستقبلية بنقرة واحدة.

قائمة التحقق من CORS في 30 ثانية

قبل تقديم بلاغ عن الخطأ، راجع هذه القائمة:

في تسع مرات من أصل عشرة، سيكون أحد هذه الأسطر الستة هو إجابتك. تحقق من ذلك بطلب OPTIONS يدوي في Apidog، قم بتصحيح تكوين الخادم، وعد إلى البناء.

الأسئلة الشائعة

لماذا أحصل على خطأ CORS في المتصفح فقط؟

لأن المتصفحات فقط هي التي تفرض CORS. تحمي سياسة نفس المصدر (same-origin policy) المستخدمين من الصفحات الخبيثة التي تقرأ بياناتهم المصدق عليها، لذا تتحقق المتصفحات من Access-Control-Allow-Origin في كل استجابة عبر المصادر. لا يوجد لدى curl وخدمات الواجهة الخلفية وعملاء سطح المكتب مثل هذه القاعدة. إذا نجح الطلب في كل مكان باستثناء المتصفح، فإن الخادم الخاص بك يفتقد أو يسيء تكوين رؤوس CORS؛ واجهة برمجة التطبيقات نفسها سليمة.

هل ينطبق CORS على Postman أو Apidog؟

لا. Postman و Apidog هما تطبيقات سطح مكتب، وليستا صفحات ويب تعمل داخل بيئة المتصفح المعزولة (browser sandbox)، لذا فإن طلباتهما تتجاوز CORS بالكامل. وهذا هو بالضبط ما يجعلهما مفيدتين لتصحيح أخطاء CORS: إنهما تظهران لك رؤوس الاستجابة الأولية للخادم دون تصفية المتصفح. يبدأ الارتباك حول اختبار Postman CORS عادة من هنا؛ فالطلب الناجح في عميل سطح المكتب لا يثبت شيئًا عن سلوك المتصفح، لكنه يعزل الطبقة الفاشلة.

هل خطأ CORS ميزة أمنية أم خطأ برمجي؟

ميزة. أخطاء CORS تعني أن المتصفح يقوم بواجبه: رفض الكشف عن بيانات الاستجابة عبر المصادر (cross-origin) للنصوص البرمجية ما لم يوافق الخادم على ذلك. تعطيل CORS في المتصفح باستخدام الأعلام (flags) أو الإضافات يخفي العرض على جهازك بينما يظل كل مستخدم يواجه نفس المشكلة. قم بإصلاح رؤوس الخادم بدلاً من ذلك.

هل يمكنني استخدام Access-Control-Allow-Origin: * في كل مكان؟

فقط لواجهات برمجة التطبيقات العامة (public APIs)، للقراءة فقط، التي لا تحتوي على ملفات تعريف الارتباط أو المصادقة. يتم رفض الرمز العام (*) كلما تم تضمين بيانات الاعتماد، وهو يعلن أن بياناتك مفتوحة لكل مصدر على الويب. لأي شيء يتطلب مصادقة، حافظ على قائمة مصادر مسموح بها (origin allowlist)، أعد المصدر المطابق، وأرسل Vary: Origin حتى تحافظ ذاكرات التخزين المؤقت المشتركة على فصل الاستجابات.

ممارسة تصميم API في Apidog

اكتشف طريقة أسهل لبناء واستخدام واجهات برمجة التطبيقات