المقدمة

الـ CORS هي اختصار => Cross origin resource sharing وتعني أن origin ما يريد التواصل مع الـ server، و أشهر طريقة لذلك هى باستخدام HTTP request.


ما هو الـ Origin ؟

الـ Domain Name الذي يريد التواصل مع الـ Server، و ملاحظة جانبية عن الفرق بين Host and Origin في الـ HTTP Request Message:

الـ Host: الـ domain name الخاص بالـ server الذي نريد التواصل معه بدون إضافة الـ protocol سواء كان http أو https

الـ Origin: الـ domain name الذي أرسل الـ request للserver، و يجب كتابة الـ protocol

مثال http request message للتوضيح:

Host: api.backend.com
Origin: https://frontend.com

معنى الـ error الذي قابلته وأنا أذاكر من كتاب redux in action مع تطوير الـ FE و الـ BE للتدريب عليهما معا:

Error code showing CORS in the browser console
Error code showing CORS in the browser console

قبل شرح معنى الـ error يجب علينا معرفة بعض المصطلحات المذكورة فيه.


ما هى الـ Preflight Request ؟

هى request يقوم الـ browser بإرسالها للـ server قبل إرسال الـ request الأصلية التي نريد إرسالها للـ server إذا كانت الـ request الخاصة بنا ليست simple request.

و ما هى الـ simple request؟

هى request يجب أن تتوافر فيها عدة معايير (موجودة بالتفصيل في مقال MDN عن  CORS المذكور في المصادر)، و إذا الـ request لم يتوفر فيه معيار واحد فقط لم يندرج تحت مسمى الـ simple request.

بالنسبة لمثالنا فالمشكلة في: Content-Type: application/json

و هذا ما يجعل الـ request not simple ، نعود للerror، بما أننا في الـ backend قد تعمدنا إرسال 404 كـ status code، فهذه إشارة للـ browser برفض الـ preflight request وبالتالي الـ browser لم يرسل الـ request الأصلية.

مقتطف من كود الـ BE:

// public/index.php
require __DIR__ . '/../vendor/autoload.php';


header("Access-Control-Allow-Origin: http://localhost:5173");
header("Access-Control-Allow-Headers: *");
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
header("Content-Type: application/json");


http_response_code(404);
echo json_encode(['errorFromTheBackEnd' => 'this is an intentional error!']);

public/index.php file content that was supposed to work (before taking the preflight request into consideration)


لكننا كنا نريد فقط إرسال 404 ك response code للـ FE لنرى كيف سيتعامل الـ FE معه.

إذا كان الأمر كذلك، فعلينا الرد على الـ preflight request بطريقة سليمة من الـ server و بعد ذلك نرسل ما نريد للـ FE.

كيف يمكننا الرد على الـ preflight request أولا حتى يتمكن الـ browser من إرسال الactual request ؟

الـ preflight request لها request method خاصة بها و هى الـ OPTIONS request method، فيمكننا كتابة condition للتأكد من إرسال الـ response الذي ينتظره الـ browser للتأكد من أن الـ server جاهز لاستقبال الـ request الخاصة بنا.

مثال على الـ condition:

require __DIR__ . '/../vendor/autoload.php';


header("Access-Control-Allow-Origin: http://localhost:5173");
header("Access-Control-Allow-Headers: *");
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
header("Content-Type: application/json");


// This will deal with the preflight request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
// return a successful response to the browser 
}


http_response_code(404);
echo json_encode(['errorFromTheBackEnd' => 'this is an intentional error!']);

content of public/index.php with adding a condition to deal with preflight request

حسنا سنكتب الcondition، لكن كيف يجب علينا الرد على الbrowser ؟

  1. يجب أن يكون الstatus code في ال2xx format.
  2. يجب الresponse headers ألا تستخدم ال* لكن يجب علينا تحديد الaccess origin, methods and headers وإلا سيتم رفض الrequest من قِبَل الbrowser بسبب CORS (الCORS هى feature في الbrowser و ليس الserver)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
 http_response_code(200);
// It is important that you exit the script so what is sent to the browser
// is the successful response only
   exit();
}

لماذا لم أواجه هذه المشكلة في laravel من قبل؟

لأن laravel framework يتعامل تلقائيا مع الـ CORS، و يمكننا تعديل ملف config/cors.php إذا أردنا بعض التعديلات من أجل use cases أخرى.

وهذا مثال لكود laravel من ملف config/cors.php

return [
 'paths' => ['api/*', 'sanctum/csrf-cookie'],
 'allowed_methods' => ['GET, POST, PUT, DELETE, OPTIONS'],
 'allowed_origins' => ['http://localhost:3000'],
 'allowed_origins_patterns' => [],
 'allowed_headers' => ['Content-Type', 'X-Auth-Token', 'Authorization', 'Origin'],
];

في الختام

الaccepted answer في السؤال الموجود في المصادر من stack overflow بها شرح وافٍ للموضوع، و في المقال أجبنا عن بعض التساؤلات التي واجهتني أثناء التعامل مع هذه المشكلة.

المصادر

Cross-Origin Resource Sharing (CORS) - HTTP | MDN
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a “preflight” request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.
OPTIONS request method - HTTP | MDN
The OPTIONS HTTP method requests permitted communication options for a given URL or server. This can be used to test the allowed HTTP methods for a request, or to determine whether a request would succeed when making a CORS preflighted request. A client can specify a URL with this method, or an asterisk (*) to refer to the entire server.

https://stackoverflow.com/questions/43871637/no-access-control-allow-origin-header-is-present-on-the-requested-resource-whe/43881141#43881141

Computer networking top down approach, ch2 application layer (doesn’t explain cors but explains important concepts like internet protocols, DNS and IPs)