Same-origin policy và CORS là 2 khái niệm dev gặp hằng ngày nhưng dễ hiểu sai. Bài này giải thích từ đầu, code Express, cảnh báo các pitfall thường gặp.
Same-origin policy
Origin = scheme + host + port. Cùng origin: https://alodev.vn:443 = https://alodev.vn:443. Khác:
- http vs https
- alodev.vn vs api.alodev.vn (subdomain khác)
- :443 vs :3000 (port khác)
SOP block:
- JS đọc response cross-origin (XHR, fetch)
- JS đọc cross-origin iframe DOM
- Cookie cross-domain (trừ khi explicit set)
SOP KHÔNG block:
- Image, script, css cross-origin (load + execute được)
- Form submit cross-origin
- Link redirect
CORS — exception cho fetch/XHR
API anh muốn JS từ alodev.vn fetch api.alodev.vn → cần CORS cho phép.
Simple request — không preflight
Method GET/POST/HEAD + content-type form/text + header chuẩn → browser gửi thẳng:
GET /users HTTP/1.1
Host: api.alodev.vn
Origin: https://alodev.vn
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://alodev.vn
Content-Type: application/json
{"users": [...]}
Server set Access-Control-Allow-Origin match → browser cho JS đọc response.
Preflight request
Method PUT/DELETE/PATCH HOẶC custom header HOẶC content-type application/json → browser gửi OPTIONS trước:
OPTIONS /users HTTP/1.1
Host: api.alodev.vn
Origin: https://alodev.vn
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://alodev.vn
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Server OK → browser gửi request thật. Cache preflight 24h (Max-Age) tránh OPTIONS mỗi request.
Implement CORS Express
npm install cors
import cors from 'cors'
// Đơn giản nhất — cho phép mọi origin (CHỈ cho public API không có auth)
app.use(cors())
// Production: whitelist
const ALLOWED = ['https://alodev.vn', 'https://www.alodev.vn']
app.use(cors({
origin: (origin, callback) => {
if (!origin || ALLOWED.includes(origin)) callback(null, true)
else callback(new Error('Not allowed by CORS'))
},
credentials: true, // gửi cookie
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'], // header browser được đọc
maxAge: 86400, // cache preflight
}))
Credentials pitfall
// ❌ Wildcard + credentials — browser reject
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Credentials', 'true')
// ✓ Origin cụ thể với credentials
res.setHeader('Access-Control-Allow-Origin', req.headers.origin) // verify whitelist trước
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Vary', 'Origin') // tránh cache nhầm
Frontend cũng cần credentials: 'include':
await fetch('https://api.alodev.vn/users', {
credentials: 'include', // gửi cookie cross-origin
})
CORS không thay CSRF protection
CORS chỉ block JS đọc response. Request vẫn được gửi:
<!-- evil.com — CORS block JS đọc response, nhưng request đã thực thi -->
<form action="https://api.alodev.vn/transfer" method="POST">
<input name="amount" value="1000000">
</form>
<script>document.forms[0].submit()</script>
<!-- Cookie tự động gửi nếu SameSite=None. CORS không liên quan -->
Đó là lý do cần CSRF token + SameSite cookie. CORS chỉ là một phần defense.
Debug CORS error
Browser console phổ biến: "Access to fetch at ... blocked by CORS policy: No 'Access-Control-Allow-Origin' header".
- Network tab → tìm request OPTIONS
- Check response headers — có Access-Control-Allow-* không?
- Check origin matching exact (case-sensitive, trailing slash)
- Credentials: phải Origin cụ thể, không wildcard
Kết luận
Same-origin policy là foundation security web — protect cookie, password manager, history. CORS là exception controlled. Hiểu preflight + credentials là 2 chỗ dễ sai nhất. Setup express cors lib với whitelist + credentials đúng. Tham khảo CSRF token để hoàn thiện defense layer.