Menu

CORS ב-Express: איך מתקנים שגיאת CORS (עם cors ובלי)

שגיאת CORS אומרת שהדפדפן חסם דף מלקרוא את התשובה של ה-API שלכם, כי התשובה לא התירה את ה-origin של הדף. מתקנים את זה בשרת: שולחים Access-Control-Allow-Origin ועונים לבקשות preflight של OPTIONS, ידנית או עם החבילה cors.

בדף הזה יש עורכים שאפשר להריץ. לערוך, להריץ ולראות את הפלט מיד.

שגיאת 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-Methodsmethods שמותרים בבקשה האמיתית (רק ב-preflight)
Access-Control-Allow-Headersheaders של בקשה שהדף רשאי לשלוח, כמו Authorization (רק ב-preflight)
Access-Control-Allow-Credentialstrue אם הדף רשאי לשלוח 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.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל