שגיאת CORS ("No 'Access-Control-Allow-Origin' header is present") אומרת שדף web ב-origin אחד, כמו http://localhost:5173, קרא ל-API שלכם ב-origin אחר, והדפדפן סירב לתת לדף לקרוא את התשובה כי התשובה לא אמרה שה-origin הזה מותר. התיקון הוא בשרת: שלחו Access-Control-Allow-Origin עם ה-origin של הדף, וענו לבקשת ה-preflight OPTIONS של הדפדפן עם ה-methods וה-headers שאתם מקבלים. ב-Express אלה כמה שורות של middleware, או app.use(cors()) מהחבילה cors.
התשובה היא רשימת המוצרים, ולשונית ה-Headers שלה מציגה access-control-allow-origin: https://app.example.com: דף ב-origin הזה רשאי לקרוא אותה. עכשיו שלחו OPTIONS /api/products/1 עם ה-headers Origin: https://app.example.com ו-Access-Control-Request-Method: PUT. זה ה-preflight שדפדפן שולח לפני PUT, והתשובה היא 204 עם ה-methods וה-headers המותרים ובלי גוף.
מה הדפדפן בודק
דפים מאותו origin לא צריכים כלום. בבקשה בין origins הדפדפן קורא את ה-headers האלה בתשובה:
| Header | משמעות |
|---|---|
Access-Control-Allow-Origin | ה-origin האחד שמותר לו לקרוא את התשובה, או * לכל אחד |
Access-Control-Allow-Methods | methods שמותרים בבקשה האמיתית (רק ב-preflight) |
Access-Control-Allow-Headers | headers של בקשה שהדף רשאי לשלוח, כמו Authorization (רק ב-preflight) |
Access-Control-Allow-Credentials | true אם הדף רשאי לשלוח cookies ולקרוא את התשובה |
Access-Control-Max-Age | כמה שניות הדפדפן רשאי לשמור ב-cache את תשובת ה-preflight |
Vary: Origin | אומר ל-caches שהתשובה תלויה ב-header הבקשה Origin |
GET פשוט או POST בסגנון טופס הולכים ישר לשרת, והדפדפן בודק את Access-Control-Allow-Origin בתשובה. השרת עדיין מריץ את הבקשה; CORS רק מחליט אם הדף רואה את התוצאה. לפני בקשה עם גוף JSON, עם header Authorization, או עם method כמו PUT או DELETE נשלחת בקשת preflight OPTIONS, והבקשה האמיתית נשלחת רק אם תשובת ה-preflight מתירה אותה. בגלל זה middleware של CORS חייב לענות ל-OPTIONS לפני שכל route או בדיקת הרשאה רצים.
להתיר כמה origins, עם credentials
Access-Control-Allow-Origin מחזיק origin אחד או *, אף פעם לא רשימה. כדי להתיר כמה, השוו את ה-header Origin של הבקשה לרשימת היתרים והחזירו אותו כשהוא תואם:
לבקשה הראשונה אין header Origin, כמו קריאה מ-curl או מדף באותו origin, ולכן היא מקבלת {"origin":null} ובלי headers של CORS. שלחו GET /api/me עם ה-header Origin: http://localhost:5173, ולשונית ה-Headers תציג את ה-origin הזה חוזר בתשובה ועוד access-control-allow-credentials: true. שנו את ה-header ל-Origin: https://evil.example ושני ה-headers של CORS נעלמים: השרת עדיין עונה, אבל דפדפן היה חוסם את הדף מלקרוא את התשובה. Access-Control-Allow-Credentials: true מאפשר לדף לשלוח cookies (עם fetch(url, { credentials: 'include' })), ודפדפנים דוחים אותו יחד עם *, ולכן credentials תמיד צריכים את ה-origin המדויק.
החבילה cors
החבילה cors עושה את אותה עבודה עם אפשרויות. התקינו אותה עם npm install cors:
const cors = require('cors');
// Any origin, no cookies: fine for a public read-only API.
app.use(cors());
// Only your frontend, with cookies.
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
// One route only.
app.get('/api/public', cors(), handler);
כשקוראים ל-cors() בלי אפשרויות, הוא שולח Access-Control-Allow-Origin: * ועונה לכל preflight. השתמשו ב-* רק לנתונים ציבוריים שלא צריכים התחברות.
טעויות נפוצות
להוסיף CORS אחרי ה-routes או אחרי middleware של אימות. בקשת ה-preflight OPTIONS לא נושאת header Authorization, אז בדיקת הרשאה שנמצאת ראשונה עונה לה עם 401 והדפדפן מדווח על שגיאת CORS. רשמו את ה-middleware של CORS ראשון.
להגדיר * עם credentials. דפדפנים דוחים את התשובה. החזירו את ה-origin המסוים במקום.
לנסות לתקן CORS ב-frontend. אפשרויות בקשה כמו mode: 'no-cors' לא מסירות את ההגבלה; הן נותנות תשובה אטומה שאי אפשר לקרוא. רק ה-headers של השרת מתקנים את זה.
איך app.use() וסדר ה-middleware עובדים, ראו middleware ב-Express.
שאלות נפוצות
למה ה-API שלי עובד ב-Postman או ב-curl אבל לא בדפדפן?
רק דפדפנים אוכפים CORS. Postman, curl ושרתים אחרים מתעלמים מה-headers של CORS, ולכן קוראים את התשובה בכל מקרה. הדפדפן מסרב להעביר את התשובה לסקריפטים של דף מ-origin אחר, אלא אם השרת מתיר את ה-origin הזה.
האם CORS הוא מנגנון אבטחה לשרת שלי?
הוא מגן על המשתמשים, לא על השרת. הוא מונע מדף זדוני לקרוא תשובות מה-API שלכם עם ה-cookies של המשתמש. כל אחד עדיין יכול לקרוא ל-API שלכם מסקריפט או משרת, כך שאימות והרשאות עדיין באחריותכם.
מה נחשב origin אחר?
origin הוא ה-scheme, ה-host וה-port יחד. http://localhost:5173 ו-http://localhost:3000 הם origins שונים כי ה-ports שונים, ו-https://example.com ו-https://api.example.com שונים ב-host.
אפשר להימנע מ-CORS בפיתוח?
כן, אם הופכים את הבקשה ל-same-origin: הגדירו את שרת הפיתוח של ה-frontend (למשל server.proxy של Vite) כך שיעביר את /api לשרת ה-Express. אז הדפדפן מדבר עם origin אחד ולא צריך headers של CORS.