أخطاء CORS في API Gateway: كيف تُفعّل CORS وما الترويسات التي يجب أن يُرسلها Lambda؟

الواجهة الأمامية تُرسل طلبًا إلى API Gateway وترجع بخطأ CORS policy: No 'Access-Control-Allow-Origin' header — هذا السيناريو يتكرر في كل مشروع تقريبًا، والمشكلة ليست دائمًا في مكان واحد. أحيانًا المشكلة في إعداد API Gateway، وأحيانًا في استجابة Lambda نفسها، وأحيانًا في الاثنين معًا. فهم الطبقتين ضروري قبل أي إصلاح.

ملخص سريع (TL;DR): أخطاء CORS في API Gateway

المشكلةالسببالحل
خطأ CORS على طلب OPTIONSلم يُفعَّل CORS في API Gatewayتفعيل CORS من Console أو عبر CLI
خطأ CORS على طلب POST/GETLambda لا تُرسل ترويسات CORS في الاستجابةإضافة Access-Control-Allow-Origin في استجابة Lambda
خطأ CORS بعد التفعيللم يُنشر (Deploy) الـ API بعد التغييرنشر API على الـ Stage المطلوب
خطأ CORS مع Credentialsالقيمة * لا تعمل مع withCredentialsتحديد النطاق بدقة بدلًا من *

كيف يعمل CORS مع API Gateway — الآلية الداخلية

المتصفح لا يُرسل الطلب الفعلي مباشرةً عند اختلاف النطاق. يُرسل أولًا طلب Preflight بالطريقة OPTIONS ليسأل الخادم: هل تسمح لهذا النطاق بالوصول؟ إذا لم يرد الخادم بالترويسات الصحيحة، يوقف المتصفح الطلب الأصلي تمامًا — لا يصل إلى Lambda، لا يصل إلى أي مكان.

في API Gateway REST API، طلب OPTIONS لا يصل إلى Lambda افتراضيًا. API Gateway نفسه يجب أن يستجيب له. هذا ما يفعله زر 'Enable CORS' في الـ Console — يُنشئ Integration وهمية لطريقة OPTIONS تُرجع الترويسات المطلوبة مباشرةً من Gateway Layer.

أما الطلب الفعلي (POST، GET، إلخ) فيصل إلى Lambda، وهنا Lambda نفسها مسؤولة عن إرسال ترويسات CORS في الاستجابة. API Gateway لا يُضيفها تلقائيًا للطلبات غير الـ OPTIONS.

sequenceDiagram participant Browser as المتصفح participant APIGW as API Gateway participant Lambda as Lambda Browser->>APIGW: OPTIONS /items (Preflight) APIGW-->>Browser: 200 OK + Access-Control-Allow-* headers Note over APIGW,Lambda: Lambda لا تُستدعى في Preflight Browser->>APIGW: POST /items (الطلب الفعلي) APIGW->>Lambda: استدعاء Lambda Lambda-->>APIGW: 200 + CORS headers في الاستجابة APIGW-->>Browser: الاستجابة النهائية Note over Browser: المتصفح يتحقق من Access-Control-Allow-Origin
  1. المتصفح يُرسل Preflight (OPTIONS) قبل الطلب الفعلي.
  2. API Gateway يعترض OPTIONS ويُرجع ترويسات CORS مباشرةً — Lambda لا تُستدعى.
  3. المتصفح يتحقق من الترويسات ثم يُرسل الطلب الفعلي (POST/GET).
  4. Lambda تُعالج الطلب وتُرجع الاستجابة — يجب أن تتضمن ترويسات CORS.
  5. المتصفح يقرأ الترويسة Access-Control-Allow-Origin ويسمح بالوصول للبيانات.

تفعيل CORS في API Gateway REST API — خطوة بخطوة

هناك فرق جوهري بين REST API وHTTP API في API Gateway. HTTP API يدعم CORS بشكل مدمج من إعدادات الـ API مباشرةً. REST API يتطلب إعداد طريقة OPTIONS يدويًا أو عبر زر 'Enable CORS' في الـ Console. الخطوات أدناه للـ REST API.

الطريقة الأولى: Console (الأسرع للاختبار)

  1. افتح API Gateway Console واختر الـ API المطلوب.
  2. من القائمة الجانبية، اختر Resources ثم حدد الـ Resource المطلوب (مثل /items).
  3. من قائمة Actions، اختر Enable CORS.
  4. في النافذة التي تظهر، حدد:
    • Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
    • Access-Control-Allow-Origin: '*' أو نطاقك المحدد مثل https://example.com
    • Access-Control-Allow-Methods: اختر الطرق المطلوبة (GET, POST, OPTIONS)
  5. اضغط Enable CORS and replace existing CORS headers.
  6. الخطوة الأهم التي يتجاهلها الجميع: اضغط Deploy API واختر الـ Stage. بدون نشر، التغييرات لا تُطبَّق.
تفعيل CORS في الـ Console يشبه كتابة قاعدة جديدة في دفتر — لا تسري حتى تُنشر. كثير من المطورين يقضون ساعات يبحثون عن المشكلة بينما التغيير موجود لكنه غير منشور.

الطريقة الثانية: AWS CLI

للإعداد البرمجي أو في بيئات CI/CD، يمكن إنشاء طريقة OPTIONS وإعداد الـ Integration والـ Method Response عبر CLI. الخطوات تتطلب تسلسلًا دقيقًا: إنشاء الطريقة، ثم الـ Integration، ثم الـ Method Response، ثم الـ Integration Response.

🔽 اضغط لعرض أوامر CLI الكاملة
# الخطوة 1: إنشاء طريقة OPTIONS على الـ Resource
aws apigateway put-method \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --http-method OPTIONS \
  --authorization-type NONE \
  --region us-east-1

# الخطوة 2: إنشاء MOCK Integration لطريقة OPTIONS
aws apigateway put-integration \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --http-method OPTIONS \
  --type MOCK \
  --request-templates '{"application/json": "{\"statusCode\": 200}"}' \
  --region us-east-1

# الخطوة 3: إنشاء Method Response بالترويسات المطلوبة
aws apigateway put-method-response \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --http-method OPTIONS \
  --status-code 200 \
  --response-parameters '{"method.response.header.Access-Control-Allow-Headers": false, "method.response.header.Access-Control-Allow-Methods": false, "method.response.header.Access-Control-Allow-Origin": false}' \
  --region us-east-1

# الخطوة 4: إنشاء Integration Response بقيم الترويسات
aws apigateway put-integration-response \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --http-method OPTIONS \
  --status-code 200 \
  --response-parameters '{"method.response.header.Access-Control-Allow-Headers": "'\''Content-Type,X-Amz-Date,Authorization,X-Api-Key'\''", "method.response.header.Access-Control-Allow-Methods": "'\''GET,POST,OPTIONS'\''", "method.response.header.Access-Control-Allow-Origin": "'\''*'\''"}' \
  --region us-east-1

# الخطوة 5: نشر الـ API على الـ Stage
aws apigateway create-deployment \
  --rest-api-id YOUR_API_ID \
  --stage-name prod \
  --region us-east-1

ترويسات CORS التي يجب أن يُرسلها Lambda في كل استجابة

هذه النقطة هي مصدر 80% من أخطاء CORS التي تظهر بعد تفعيل CORS في Gateway. المطور يُفعّل CORS، يختبر، ويجد الخطأ ما زال موجودًا — لأن Lambda لا تُرسل الترويسات في الاستجابة الفعلية.

API Gateway لا يُضيف ترويسات CORS تلقائيًا على استجابات Lambda. كل استجابة من Lambda يجب أن تتضمن Access-Control-Allow-Origin على الأقل.

مثال: استجابة Lambda صحيحة (Python)

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
            'Content-Type': 'application/json'
        },
        'body': json.dumps({'message': 'success'})
    }

مثال: استجابة Lambda صحيحة (Node.js)

exports.handler = async (event) => {
    return {
        statusCode: 200,
        headers: {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ message: 'success' })
    };
};

إذا كانت Lambda تُرجع خطأ (مثل استثناء غير معالج)، API Gateway يُرجع استجابة خطأ بدون ترويسات CORS — المتصفح يرى خطأ CORS بدلًا من رسالة الخطأ الفعلية. هذا يُخفي الخطأ الحقيقي ويجعل التشخيص أصعب.

graph TD A[Lambda تُعالج الطلب] --> B{هل نجح التنفيذ؟} B -->|نعم| C[استجابة 200
مع ترويسات CORS] B -->|خطأ غير معالج| D[استجابة 500
بدون ترويسات CORS] B -->|خطأ معالج| E[استجابة 4xx/5xx
مع ترويسات CORS] C --> F[المتصفح يقبل البيانات] D --> G[المتصفح يُبلّغ عن خطأ CORS] E --> F G --> H[الخطأ الحقيقي مخفي]
  1. Lambda تُرجع استجابة ناجحة مع ترويسات CORS — المتصفح يقبل البيانات.
  2. Lambda تُرجع خطأ بدون ترويسات CORS — المتصفح يُبلّغ عن خطأ CORS بدلًا من الخطأ الفعلي.
  3. الحل: معالجة الأخطاء في Lambda يجب أن تتضمن ترويسات CORS دائمًا.

تشخيص أخطاء CORS في API Gateway — خطوات منهجية

الخطوة 1: تحقق من وجود طريقة OPTIONS على الـ Resource

نقطة البداية الصحيحة هي التحقق من أن API Gateway يمتلك طريقة OPTIONS مُعدّة على الـ Resource المطلوب — لأن غيابها يعني أن Preflight سيفشل قبل أن يصل أي شيء إلى Lambda.

aws apigateway get-resource \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --region us-east-1

في الناتج، تحقق من وجود OPTIONS ضمن resourceMethods. إذا لم تجدها، CORS غير مُفعَّل بشكل صحيح.

الخطوة 2: تحقق من أن الـ API منشور على الـ Stage الصحيح

التغييرات في API Gateway لا تُطبَّق حتى تُنشر. هذه الخطوة تُغطي الفجوة التي لا تكشفها الخطوة الأولى — يمكن أن تكون الإعدادات صحيحة في الـ Console لكن الـ Stage يعمل بنسخة قديمة.

aws apigateway get-stage \
  --rest-api-id YOUR_API_ID \
  --stage-name prod \
  --region us-east-1

قارن deploymentId في الناتج مع آخر Deployment منشور للتأكد من تطابقهما.

الخطوة 3: اختبر طلب OPTIONS مباشرةً

بدلًا من الاعتماد على المتصفح لتشخيص المشكلة، أرسل طلب OPTIONS يدويًا لترى الترويسات التي يُرجعها API Gateway فعليًا.

curl -X OPTIONS https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/prod/YOUR_RESOURCE \
  -H 'Origin: https://your-frontend-domain.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: Content-Type' \
  -v 2>&1 | grep -i 'access-control'

إذا لم تظهر ترويسات Access-Control-* في الناتج، المشكلة في إعداد API Gateway. إذا ظهرت في OPTIONS لكن الخطأ ما زال موجودًا، المشكلة في استجابة Lambda.

الخطوة 4: تحقق من استجابة Lambda مباشرةً

هذه الخطوة تعزل Lambda عن API Gateway لتحديد ما إذا كانت الترويسات غائبة من مصدرها — وهو ما لا يمكن اكتشافه من اختبار الـ Endpoint العام وحده.

aws lambda invoke \
  --function-name YOUR_FUNCTION_NAME \
  --payload '{}' \
  --cli-binary-format raw-in-base64-out \
  --region us-east-1 \
  response.json && cat response.json

تحقق من أن الناتج يتضمن Access-Control-Allow-Origin في كائن headers.

HTTP API مقابل REST API — فرق جوهري في إعداد CORS

إذا كنت تستخدم HTTP API (وليس REST API)، إعداد CORS مختلف تمامًا. HTTP API يدعم CORS من إعدادات الـ API مباشرةً دون الحاجة لإنشاء طريقة OPTIONS يدويًا.

# إعداد CORS على HTTP API
aws apigatewayv2 update-api \
  --api-id YOUR_HTTP_API_ID \
  --cors-configuration AllowOrigins='https://your-frontend-domain.com',AllowMethods='GET,POST,OPTIONS',AllowHeaders='Content-Type,Authorization' \
  --region us-east-1

في HTTP API، API Gateway يُضيف ترويسات CORS تلقائيًا على جميع الاستجابات بما فيها استجابات Lambda — لكن ترويسات Lambda تتجاوز إعدادات الـ API إذا تعارضت. تجنب إرسال ترويسات CORS من Lambda في HTTP API لتفادي التعارض.

حالة حقيقية: خطأ CORS يختفي ثم يعود

في أحد المشاريع، الواجهة الأمامية كانت تعمل بشكل صحيح في بيئة التطوير ثم تفشل في الإنتاج بخطأ CORS متقطع. الافتراض الأول كان أن إعداد CORS في API Gateway غير صحيح — تم التحقق منه وكان سليمًا.

الافتراض الثاني كان أن المشكلة في Cache — تم مسح الـ Cache ولم يتغير شيء.

السبب الفعلي: Lambda كانت تُرجع خطأ 500 في حالات معينة (timeout على قاعدة البيانات) بدون ترويسات CORS. المتصفح كان يرى خطأ CORS بدلًا من خطأ الـ 500 الفعلي. الحل كان إضافة معالج أخطاء شامل في Lambda يُرجع ترويسات CORS حتى في حالات الفشل.

graph LR A[خطأ CORS متقطع] --> B[الافتراض: إعداد CORS خاطئ] B --> C{التحقق من OPTIONS} C -->|الإعداد صحيح| D[الافتراض: مشكلة Cache] D --> E{مسح Cache} E -->|المشكلة مستمرة| F[فحص استجابات Lambda] F --> G[Lambda تُرجع 500
بدون ترويسات CORS] G --> H[الحل: معالجة الأخطاء
تتضمن ترويسات CORS دائمًا]

الدرس: خطأ CORS المتقطع في الإنتاج غالبًا يخفي خطأً آخر في Lambda، وليس مشكلة في إعداد CORS نفسه.

إعداد CORS مع بيانات الاعتماد (Credentials)

إذا كانت الواجهة الأمامية تُرسل Cookies أو Authorization headers مع withCredentials: true، قيمة * في Access-Control-Allow-Origin لا تعمل. المتصفح يرفضها صراحةً.

يجب تحديد النطاق بدقة وإضافة Access-Control-Allow-Credentials: true:

return {
    'statusCode': 200,
    'headers': {
        'Access-Control-Allow-Origin': 'https://your-frontend-domain.com',
        'Access-Control-Allow-Credentials': 'true',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization',
        'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
        'Content-Type': 'application/json'
    },
    'body': json.dumps({'message': 'success'})
}

الخلاصة والخطوات التالية لحل أخطاء CORS في API Gateway

حل أخطاء CORS في API Gateway يتطلب التحقق من طبقتين: إعداد OPTIONS في API Gateway، وترويسات الاستجابة في Lambda. تفعيل CORS في الـ Console وحده لا يكفي — Lambda يجب أن تُرسل الترويسات في كل استجابة، بما فيها استجابات الأخطاء.

للمزيد من التفاصيل، راجع:

مسرد المصطلحات

المصطلحالتعريف
CORSCross-Origin Resource Sharing — آلية في المتصفح تتحكم في الطلبات بين نطاقات مختلفة
Preflight Requestطلب OPTIONS يُرسله المتصفح تلقائيًا قبل الطلب الفعلي للتحقق من صلاحيات CORS
Integration Responseالاستجابة التي يُرجعها API Gateway من الـ Integration (Lambda أو MOCK) قبل إرسالها للعميل
MOCK IntegrationIntegration في API Gateway يُرجع استجابة ثابتة بدون استدعاء أي Backend — يُستخدم لطريقة OPTIONS
Stage Deploymentعملية نشر التغييرات في API Gateway على بيئة محددة (dev, prod) لتصبح سارية المفعول

تعليقات

المشاركات الشائعة من هذه المدونة

استرجاع معرّف نسخة EC2 عبر خدمة البيانات الوصفية IMDSv2

أوضاع سعة DynamoDB: متى تختار On-Demand ومتى تختار Provisioned؟

فهم مهلة الرؤية في SQS: لماذا تُعالَج رسائلك مرتين؟