1. کتابخانه
  2. HTTP והרשת
  3. כותרות HTTP

به‌روزرسانی شده ۱ ماه پیش

אתה שולח בקשת fetch. השרת מגיב. ואז הדפדפן שלך זורק את התשובה לפח.

Access to fetch at 'https://api.example.com' from origin 'https://app.example.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

זהו CORS — שיתוף משאבים בין מקורות שונים (Cross-Origin Resource Sharing). והדבר שהופך את התסכול להבנה: השרת כבר עיבד את הבקשה שלך. הוא הריץ את הקוד שלך, נגע במסד הנתונים שלך, החזיר את הנתונים שלך. CORS רק מחליט אם ה-JavaScript שלך יוכל לראות מה קרה.

CORS הוא הדפדפן שמשחק את תפקיד השומר בכניסה, בודק את רשימת האורחים בכל תגובה.

למה הדפדפנים עושים את זה

דמיין שאתה מחובר לחשבון הבנק שלך בכתובת https://mybank.com. הדפדפן שלך מחזיק את ה-cookie של ה-session שלך. עכשיו אתה מבקר ב-https://sketchy-site.com, וקוד JavaScript סמוי מופעל:

fetch('https://mybank.com/api/transfer', {
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify({ to: 'attacker', amount: 10000 })
});

ללא CORS, זה עובד. הדפדפן שלך שולח אוטומטית את ה-cookies של הבנק עם הבקשה. ההעברה מתבצעת. התוקף מקבל את הכסף שלך.

CORS מונע זאת על ידי דרישה ש-https://mybank.com יאמר במפורש "כן, אני סומך על בקשות מ-https://sketchy-site.com". מאחר שאף בנק לגיטימי לא יאמר זאת, הדפדפן חוסם את התגובה — וחשוב יותר, חוסם לחלוטין בקשות עם פרטי אישור ללא אישור מראש.

מה מרכיב מקור

מקור הוא שילוב של סכמה, שם המארח ויציאה:

  • https://example.com ו-http://example.com — מקורות שונים (סכמה שונה)
  • https://example.com ו-https://api.example.com — מקורות שונים (שם מארח שונה)
  • https://example.com:443 ו-https://example.com:8443 — מקורות שונים (יציאה שונה)

אותו מקור פירושו שכל שלושת המרכיבים תואמים בדיוק.

הכותרות השולטות בהכל

Access-Control-Allow-Origin

כרטיס הכניסה הבסיסי:

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

זה אומר: "JavaScript מ-https://app.example.com רשאי לגשת לתגובה זו." כל מקור אחר ייחסם.

התו הכללי מאפשר לכולם:

Access-Control-Allow-Origin: *

מתאים לנתונים ציבוריים באמת. מסוכן לכל דבר אחר. וחשוב: תווים כלליים לא עובדים עם פרטי אישור.

Access-Control-Allow-Credentials

כברירת מחדל, בקשות בין מקורות אינן כוללות cookies או כותרות Authorization. כדי לאפשר אותן:

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

שניהם נדרשים. ושימו לב: אין תו כללי. כאשר פרטי אישור מעורבים, השרת חייב לציין את המקור הספציפי שהוא סומך עליו. זה מכוון. אי אפשר לומר "אני סומך על כולם עם האימות של המשתמשים שלי."

Access-Control-Allow-Methods

אילו שיטות HTTP מותרות:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE

Access-Control-Allow-Headers

אילו כותרות בקשה מותרות:

Access-Control-Allow-Headers: Content-Type, Authorization

כברירת מחדל, רק מספר מועט של כותרות "בטוחות" מותרות. ברגע שאתה מוסיף Authorization או Content-Type: application/json, אתה זקוק לאישור מפורש.

Access-Control-Max-Age

כמה זמן (בשניות) לשמור במטמון את בדיקת ההרשאה:

Access-Control-Max-Age: 86400

זה חשוב בגלל בקשות preflight.

Access-Control-Expose-Headers

כברירת מחדל, JavaScript יכול לראות רק מעט כותרות תגובה. כדי לחשוף כותרות מותאמות אישית:

Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining

בקשות פשוטות לעומת Preflight

חלק מהבקשות הן "פשוטות" — הדפדפן שולח אותן ישירות ובודק את CORS על התגובה:

  • שיטת GET, HEAD, או POST
  • רק כותרות סטנדרטיות (Accept, Content-Type עם ערכי טופס, וכו')
  • Content-Type מוגבל ל-application/x-www-form-urlencoded, multipart/form-data, או text/plain

כל שאר המקרים מפעיל preflight — בקשת OPTIONS שמבקשת אישור לפני הבקשה האמיתית:

OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization

השרת מגיב עם מה שהוא מאפשר:

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

אם ה-preflight עובר, הדפדפן שולח את הבקשה האמיתית. אם הוא נכשל, הבקשה האמיתית לא מתרחשת כלל.

זו הסיבה ש-Access-Control-Max-Age חשוב — ללא שמירה במטמון, כל קריאת API עם כותרות מותאמות אישית דורשת שני הלוך ושוב.

שגיאות נפוצות ומה הן אומרות

"No 'Access-Control-Allow-Origin' header is present"

השרת לא כלל כותרות CORS בכלל. CORS לא מוגדר, או שהוא אינו מוגדר עבור המקור שלך.

"The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'"

אתה שולח פרטי אישור (cookies, Authorization) אבל השרת החזיר *. הוא חייב להחזיר את המקור המדויק שלך.

"Method PUT is not allowed by Access-Control-Allow-Methods"

תגובת ה-preflight לא כללה PUT. הוסף אותו לשיטות המותרות.

"Request header field authorization is not allowed by Access-Control-Allow-Headers"

תגובת ה-preflight לא אפשרה את כותרת Authorization. הוסף אותה.

ניפוי שגיאות

פתח את DevTools. שתי לשוניות חשובות:

Console: מציגה את שגיאת CORS עם פרטים על מה שנכשל.

Network: מציגה את הבקשות בפועל. חפש:

  • את ה-OPTIONS preflight (אם קיים)
  • את כותרות התגובה (או היעדרן)
  • האם הבקשה נחסמה לעומת האם התגובה נחסמה

זכור: אם אתה רואה את הבקשה בלשונית Network עם תגובה, השרת עיבד אותה. CORS רק מנע מה-JavaScript שלך לקרוא את התוצאה.

הגדרת שרת

Express/Node.js:

const cors = require('cors');
app.use(cors({
    origin: 'https://app.example.com',
    credentials: true
}));

Nginx:

location /api {
    add_header Access-Control-Allow-Origin https://app.example.com always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE' always;
    add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
    
    if ($request_method = OPTIONS) {
        return 204;
    }
}

הוראת always מבטיחה שהכותרות מופיעות גם בתגובות שגיאה — אחרת שגיאת 500 עלולה להיות חסרת כותרות CORS, מה שמקשה על ניפוי שגיאות.

היבטי אבטחה

CORS הוא אבטחת דפדפן, לא אבטחת שרת. כל מי שמשתמש ב-curl, Postman, או לקוח מותאם אישית עוקף את CORS לחלוטין. הוא מגן על משתמשים מאתרים זדוניים, לא על ה-API שלך מגורמים זדוניים.

לעולם אל תחזיר את כותרת Origin באופן עיוור:

// מסוכן — מאפשר כל מקור
response.header('Access-Control-Allow-Origin', request.headers.origin);

שמור על רשימת היתרים:

const allowed = ['https://app.example.com', 'https://admin.example.com'];
const origin = request.headers.origin;
if (allowed.includes(origin)) {
    response.header('Access-Control-Allow-Origin', origin);
}

פרטי אישור דורשים זהירות מיוחדת. Access-Control-Allow-Credentials: true פירושו "למקור הזה מותר לשלוח בקשות מאומתות בשם המשתמש." הענק הרשאה זו רק למקורות שאתה שולט בהם לחלוטין.

תווים כלליים הם לנתונים ציבוריים בלבד. אם ה-API שלך דורש אימות, * הוא שגוי.

המודל המנטלי

CORS הוא שיחה בין הדפדפן שלך לשרת:

  1. דפדפן: "אני JavaScript ממקור X, רוצה לגשת למקור Y"
  2. שרת: "הנה התגובה שלי, והנה מי שאני סומך עליו" (דרך כותרות)
  3. דפדפן: "האם המקור X נמצא ברשימה? אם כן — ה-JavaScript מקבל את הנתונים. אם לא — חסום."

השרת תמיד מעבד את הבקשה. CORS שולט רק בשאלה האם הדפדפן ישתף את התגובה עם JavaScript.

זו הסיבה שטעויות CORS מרגישות הפוכות — אתה רואה ב-DevTools שהשרת הגיב בהצלחה, ובכל זאת הקוד שלך לא יכול לגשת אליו. התגובה קיימת. הדפדפן פשוט לא מרשה לך לקבל אותה.

שאלות נפוצות על CORS

למה הבקשה שלי עובדת ב-Postman אבל נכשלת בדפדפן?

Postman אינו דפדפן — הוא אינו אוכף CORS. CORS הוא תכונת אבטחה ספציפית לדפדפן שמונעת מ-JavaScript במקור אחד לגשת לתגובות ממקור אחר. כלים כמו curl ו-Postman מדלגים על בדיקה זו לחלוטין.

למה אני מקבל שגיאות CORS על localhost בזמן פיתוח?

http://localhost:3000 ו-http://localhost:8080 הם מקורות שונים (יציאות שונות). הממשק הקדמי והשרת שלך זקוקים להגדרת CORS מתאימה אפילו בפיתוח. מסגרות רבות כוללות מצבי פיתוח שמטפלים בכך אוטומטית.

האם אפשר להשבית את CORS?

לא ממש. ניתן להתקין הרחבות דפדפן שמסירות כותרות CORS לצורכי פיתוח, אבל זה משפיע רק על הדפדפן שלך. המשתמשים שלך עדיין יתקלו בשגיאות CORS. הפתרון האמיתי הוא הגדרת שרת נכונה.

למה הוספת כותרת Authorization פתאום שוברת את הבקשה שלי?

Authorization אינה כותרת "בטוחה", ולכן היא מפעילה בקשת preflight מסוג OPTIONS. השרת שלך חייב לטפל בבקשות OPTIONS ולהחזיר Access-Control-Allow-Headers מתאים הכולל Authorization.

למה לא ניתן להשתמש בתו כללי (*) עם פרטי אישור?

זה מכוון מבחינת אבטחה. אפשור פרטי אישור עם תו כללי פירושו "כל אתר יכול לשלוח בקשות מאומתות בשם המשתמש הזה" — חור אבטחה עצום. המפרט דורש התאמת מקור מפורשת כאשר פרטי אישור מעורבים.

آیا این صفحه مفید بود؟

😔
🤨
😃
כותרות CORS • کتابخانه • Connected