
اگه با فرانتاند کار کرده باشی، احتمالاً یه روز با یه ارور خیلی اعصابخردکن مواجه شدی:
Blocked by CORS policy
اولش احتمالاً با خودت گفتی: «خب بابا! من فقط میخوام یه API رو صدا بزنم، دیگه چرا گیر میدی؟!» 😐
ولی واقعاً چرا مرورگر همچین کاری میکنه؟
اصلاً CORS چرا به وجود اومده؟
برای جواب دادن به این سؤال، باید اول یه چیز مهمتر رو بفهمیم: Same-Origin Policy.
مرورگر از قدیم یه قانون امنیتی مهم داره به اسم Same-Origin Policy یا به اختصار SOP.
ایدهاش خیلی سادهست:
یه سایت نباید همینجوری بتونه به اطلاعات سایتهای دیگه دسترسی داشته باشه.
فرض کن وارد سایت بانک شدی:
https://bank.com
و توی همون مرورگر هم وارد حساب بانکیت هستی.
حالا یه سایت دیگه رو باز میکنی:
https://evil.com
اگر مرورگر هیچ محدودیتی نداشت، سایت evil.com میتونست از طرف تو درخواستهایی به bank.com بفرسته و شاید به اطلاعات حساس حسابت دسترسی پیدا کنه.
اینجا مرورگر میگه:
«نه عزیزم، همچین خبری نیست!» 😄
و جلوی دسترسی سایتها به منابع Originهای دیگه رو میگیره.
برای اینکه بفهمیم CORS چطوری کار میکنه، باید بدونیم Origin چیه.
Origin از سه بخش تشکیل میشه:
Protocol
Host
Port
مثلاً:
https://example.com:443
اینجا داریم:
Protocol → https Host → example.com Port → 443
حالا اینا رو ببین:
https://example.com https://api.example.com http://example.com https://example.com:3000
اینها Origin یکسانی ندارن.
مثلاً این دو تا:
https://example.com https://api.example.com
چون Host متفاوت دارن، از نظر مرورگر دو Origin متفاوت محسوب میشن.
فرض کن فرانتاندت روی این آدرس اجرا شده:
http://localhost:3000
و بکاندت روی این آدرس قرار داره:
http://localhost:8000
تو از فرانتاند میخوای این درخواست رو بفرستی:
fetch("http://localhost:8000/api/users")
از دید ما که هر دو روی localhost هستن و خیلی عادی به نظر میرسن.
ولی مرورگر میگه:
3000 ≠ 8000
پس این دو تا Origin متفاوتن.
اینجاست که CORS وارد ماجرا میشه.
CORS مخفف این عبارته:
Cross-Origin Resource Sharing
یعنی:
اشتراکگذاری منابع بین Originهای مختلف
در واقع CORS یه راه استاندارد به سرور میده که به مرورگر اعلام کنه:
«من اجازه میدم فلان Origin به منابع من دسترسی داشته باشه.»
مثلاً سرور میتونه در Response این Header رو برگردونه:
Access-Control-Allow-Origin: http://localhost:3000
یعنی:
«به درخواستهایی که از
localhost:3000میان اجازه دسترسی بده.»
حالا مرورگر میبینه سرور اجازه داده و اجازه میده JavaScript فرانتاند Response رو بخونه.
CORS در اصل قانون سرور نیست؛ رفتار امنیتی مرورگره.
یعنی این تصور اشتباهه که:
«CORS اجازه نمیده Request ارسال بشه.»
در خیلی از درخواستها، مرورگر ممکنه درخواست رو واقعاً به سرور بفرسته، ولی بعد اجازه نده JavaScript صفحه Response رو بخونه.
این تفاوت خیلی مهمه.
مثلاً ممکنه توی DevTools ببینی Request به سرور رفته، ولی مرورگر بعدش بهت بگه:
Access to fetch at ... has been blocked by CORS policy
پس مشکل الزاماً این نیست که Request به سرور نرسیده؛ مشکل میتونه این باشه که مرورگر اجازه نداده کد JavaScript به Response دسترسی داشته باشه.
چون مرورگر نمیتونه برای هر API حدس بزنه که چه کسی اجازه دسترسی داره.
فرض کن یه API داریم:
https://api.example.com/users
سرور میتونه بگه:
Access-Control-Allow-Origin: https://mywebsite.com
یعنی:
فقط سایت
mywebsite.comاجازه داره Response این API رو از داخل مرورگر بخونه.
ولی اگر سرور این Header رو نفرسته، مرورگر طبق قوانین CORS اجازه دسترسی JavaScript به Response رو نمیده.
اینجا یکی از قسمتهای جذاب CORS میرسیم: Preflight Request.
بعضی درخواستهای Cross-Origin قبل از اینکه Request اصلی ارسال بشه، یه درخواست OPTIONS میفرستن.
مثلاً مرورگر میپرسه:
OPTIONS /api/users Origin: https://mywebsite.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type
در واقع مرورگر داره از سرور میپرسه:
«داداش، من از این Origin میام و میخوام با متد POST و این Headerها درخواست بزنم. اوکیه؟»
سرور اگر مشکلی نداشته باشه، مثلاً جواب میده:
Access-Control-Allow-Origin: https://mywebsite.com Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: Content-Type
بعد مرورگر میگه:
«اوکی، بزن بریم.» 😎
و Request اصلی رو ارسال میکنه.
چون بعضی درخواستها اصطلاحاً Simple Request محسوب میشن و نیازی به Preflight ندارن.
مثلاً یک GET ساده ممکنه بدون OPTIONS قبلی ارسال بشه.
اما اگر شرایط درخواست از قوانین درخواست ساده خارج بشه، مرورگر ممکنه قبلش Preflight انجام بده.
برای همین بعضی وقتها توی Network میبینی:
OPTIONS POST
و میگی:
«این OPTIONS دیگه از کجا پیداش شد؟!» 😂
اون همون Preflight مربوط به CORSه.
واقعیت اینه که CORS یکی از چیزهاییه که اول کار خیلی رو مخ به نظر میاد.
ولی اگر CORS وجود نداشت، یه سایت مخرب میتونست خیلی راحت از داخل مرورگر کاربر به سرویسهای دیگه درخواست بفرسته و Response اونها رو بخونه.
پس داستان اصلی اینه:
Browser ↓ "آیا این سایت اجازه داره؟" ↓ Server ↓ "آره، اجازه داره." ↓ Browser ↓ "باشه، Response رو در اختیار JavaScript میذارم."
در واقع CORS یه مکانیزم اجازه دادن کنترلشده روی محدودیت Same-Origin Policy محسوب میشه.
این یکی هم خیلی مهمه.
CORS نمیگه:
«آیا این کاربر اجازه دسترسی به اطلاعات رو داره؟»
این کار وظیفه Authentication و Authorization هست.
CORS بیشتر میگه:
«آیا JavaScriptای که از این Origin اجرا شده، اجازه داره Response این سرور رو بخونه؟»
پس اینا دو موضوع جدا هستن.
ممکنه CORS کاملاً درست تنظیم شده باشه، ولی کاربر Authentication نداشته باشه و API جواب 401 Unauthorized بده.
برعکسش هم ممکنه Authentication درست باشه، ولی مرورگر به خاطر CORS اجازه دسترسی به Response رو نده.
اینجا یکی از سؤالهای معروفه:
«API توی Postman کار میکنه، ولی توی مرورگر CORS Error میگیرم. چرا؟»
چون CORS یک مکانیزم مربوط به مرورگره.
Postman مثل مرورگر رفتار نمیکنه و محدودیتهای امنیتی مرورگر رو به همون شکل اعمال نمیکنه.
پس ممکنه:
Postman → API → OK ✅ Browser → API → CORS Error ❌
این لزوماً به معنی خراب بودن API نیست.
اگر بخوایم همه داستان رو در یک جمله خلاصه کنیم:
CORS داریم تا مرورگر بتونه بین درخواستهای Cross-Origin، دسترسی JavaScript به Responseها رو به شکل کنترلشده مدیریت کنه.
بدون این محدودیتها، یک سایت مخرب میتونست از داخل مرورگر کاربر به سرویسهای مختلف درخواست بفرسته و اطلاعات Response رو بخونه.
پس دفعه بعد که با این خطا مواجه شدی:
Blocked by CORS policy
به جای اینکه بگی:
«باز این CORS لعنتی!» 😂
یادت باشه مرورگر داره میگه:
«من فقط دارم مطمئن میشم این سایت واقعاً اجازه داره جواب این سرور رو بخونه.»
و دقیقاً به همین دلیل، CORS وجود داره.