1. CORS là gì?#
CORS (Cross-Origin Resource Sharing) là một cơ chế cho phép một trang web truy cập tài nguyên từ một domain khác với domain của chính trang web đó. Nói cách khác, CORS là “người gác cổng” kiểm soát các request cross-origin trong trình duyệt.
Ví dụ thực tế:#
Frontend: https://myapp.com
API: https://api.myapp.com ← Cross-origin!
Khi frontend tại myapp.com gọi API tại api.myapp.com, đây là một cross-origin request. Nếu không có CORS, trình duyệt sẽ chặn request này.
Origin là gì?#
Một origin được xác định bởi 3 yếu tố:
- Protocol (http, https)
- Domain (example.com, api.example.com)
- Port (80, 443, 3000, 8080)
Hai URL có cùng origin khi cả 3 yếu tố trên đều giống hệt nhau.
| URL 1 | URL 2 | Cùng Origin? |
|---|---|---|
https://site.com/page1 |
https://site.com/page2 |
✓ |
https://site.com |
http://site.com |
✗ (khác protocol) |
https://site.com |
https://api.site.com |
✗ (khác domain) |
https://site.com |
https://site.com:8080 |
✗ (khác port) |
2. Tại sao cần CORS? — Same-Origin Policy#
Để hiểu CORS, trước tiên phải hiểu Same-Origin Policy (SOP) — chính sách bảo mật nền tảng của web.
Same-Origin Policy là gì?#
SOP là quy tắc bảo mật mặc định của trình duyệt: một script từ origin A không thể đọc dữ liệu từ origin B. Điều này ngăn chặn:
- Trang web độc hại đọc dữ liệu từ tài khoản ngân hàng của bạn (đã đăng nhập ở tab khác)
- Một website lạ gửi request đến API nội bộ của công ty bạn
- Kẻ tấn công đánh cắp thông tin cá nhân qua cross-site scripting
Vấn đề phát sinh#
SOP rất tốt cho bảo mật, nhưng trong thực tế phát triển web hiện đại, chúng ta cần cross-origin requests một cách hợp pháp:
- Frontend (React, Vue) chạy trên
localhost:3000, gọi API tạilocalhost:8000 - Microservices triển khai trên nhiều domain khác nhau
- CDN phục vụ static assets từ domain riêng
- Nhúng widget, font, hoặc iframe từ bên thứ ba
→ CORS ra đời để giải quyết mâu thuẫn này: cho phép server “nới lỏng” SOP một cách có kiểm soát.
3. CORS hoạt động như thế nào?#
CORS hoạt động thông qua các HTTP headers. Khi trình duyệt gửi một cross-origin request, server phản hồi với các header CORS để cho biết liệu request có được phép hay không.
Luồng hoạt động cơ bản:#
┌──────────┐ Request + Origin ┌──────────┐
│ Browser │ ──────────────────────────→│ Server │
│ │ │ │
│ (kiểm │ Response + CORS Headers │ (quyết │
│ tra │ ←───────────────────────── │ định) │
│ CORS) │ │ │
└──────────┘ └──────────┘
- Trình duyệt tự động thêm header
Originvào request - Server kiểm tra origin và trả về CORS headers (nếu được phép)
- Trình duyệt kiểm tra CORS headers và quyết định cho phép/từ chối
Nếu server không trả về CORS headers phù hợp, trình duyệt sẽ chặn response và báo lỗi CORS trong console.
4. Preflight Request là gì?#
Preflight request là một request OPTIONS được trình duyệt tự động gửi trước request thực tế để kiểm tra xem server có cho phép cross-origin request hay không.
Khi nào preflight được kích hoạt?#
Preflight được gửi khi request không phải là simple request. Một request được coi là “simple” khi thỏa mãn tất cả điều kiện sau:
| Điều kiện | Simple Request |
|---|---|
| Method | GET, HEAD, POST |
| Content-Type | application/x-www-form-urlencoded, multipart/form-data, text/plain |
| Headers | Chỉ các header “an toàn” (Accept, Accept-Language, Content-Language, v.v.) |
Bất kỳ sai khác nào cũng kích hoạt preflight. Ví dụ:
- Gửi
Content-Type: application/json→ preflight - Gửi header
Authorization→ preflight - Dùng method
PUT,PATCH,DELETE→ preflight
Luồng Preflight:#
┌──────────┐ OPTIONS /api/data ┌──────────┐
│ Browser │ ──────────────────────────────→│ Server │
│ │ Access-Control-Allow-Origin │ │
│ │ ←──────────────────────────────│ │
│ │ (nếu OK) │ │
│ │ POST /api/data (request thật) │ │
│ │ ──────────────────────────────→│ │
│ │ Response + CORS Headers │ │
│ │ ←──────────────────────────────│ │
└──────────┘ └──────────┘
5. Các header CORS quan trọng#
Response Headers (Server → Browser)#
| Header | Mô tả | Ví dụ |
|---|---|---|
Access-Control-Allow-Origin |
Chỉ định origin nào được phép truy cập | * hoặc https://myapp.com |
Access-Control-Allow-Methods |
Các HTTP method được phép | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers |
Các request header được phép | Content-Type, Authorization |
Access-Control-Allow-Credentials |
Cho phép gửi cookie/credentials | true |
Access-Control-Max-Age |
Thời gian cache preflight (giây) | 3600 |
Access-Control-Expose-Headers |
Các response header JS được đọc | X-Total-Count, Link |
Request Headers (Browser → Server)#
| Header | Mô tả |
|---|---|
Origin |
Origin của trang web gửi request (tự động) |
Access-Control-Request-Method |
Method sẽ dùng trong request thật (preflight) |
Access-Control-Request-Headers |
Headers sẽ dùng trong request thật (preflight) |
Ví dụ Response với CORS Headers:#
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
6. CORS trong thực tế#
Cấu hình CORS với Express.js (Node.js)#
const express = require('express');
const cors = require('cors');
const app = express();
// Cách 1: Cho phép tất cả origins (chỉ dùng cho dev)
app.use(cors());
// Cách 2: Cấu hình chi tiết (khuyên dùng cho production)
app.use(cors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
Cấu hình CORS với Django (Python)#
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
"https://admin.myapp.com",
]
CORS_ALLOW_CREDENTIALS = True
Cấu hình CORS với Spring Boot (Java)#
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://myapp.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
Cấu hình CORS với Nginx (Reverse Proxy)#
location /api/ {
proxy_pass http://backend:8000;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials "true";
if ($request_method = 'OPTIONS') {
return 204;
}
}
7. Các lỗi CORS thường gặp và cách khắc phục#
Lỗi 1: Access-Control-Allow-Origin header missing#
Access to fetch at 'https://api.example.com/data' from origin
'https://myapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Nguyên nhân: Server không trả về CORS headers.
Cách khắc phục: Cấu hình server để trả về Access-Control-Allow-Origin phù hợp.
Lỗi 2: Origin không được phép#
The 'Access-Control-Allow-Origin' header has a value
'https://allowed.com' that is not equal to the supplied origin.
Nguyên nhân: Origin của bạn không nằm trong danh sách được phép.
Cách khắc phục: Thêm origin vào whitelist của server.
Lỗi 3: Preflight bị từ chối#
Response to preflight request doesn't pass access control check:
It does not have HTTP ok status.
Nguyên nhân: Server không xử lý OPTIONS request hoặc trả về lỗi.
Cách khắc phục: Đảm bảo server xử lý OPTIONS request và trả về 2xx.
Lỗi 4: Credentials + Wildcard Origin#
The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is 'include'.
Nguyên nhân: Không thể dùng * khi credentials: true.
Cách khắc phục: Chỉ định origin cụ thể thay vì wildcard *.
Lỗi 5: Header không được phép#
Request header field X-Custom-Header is not allowed
by Access-Control-Allow-Headers in preflight response.
Nguyên nhân: Request chứa header không có trong Access-Control-Allow-Headers.
Cách khắc phục: Thêm header vào danh sách cho phép.
8. CORS vs JSONP vs Proxy — So sánh giải pháp#
| Giải pháp | Ưu điểm | Nhược điểm | Khi nào dùng? |
|---|---|---|---|
| CORS | Chuẩn W3C, bảo mật, linh hoạt | Cần cấu hình server | Giải pháp chính cho mọi cross-origin API |
| JSONP | Hỗ trợ trình duyệt cũ | Chỉ GET, rủi ro XSS | Legacy systems (không khuyến nghị) |
| Proxy Server | Không cần cấu hình CORS | Thêm chi phí, độ trễ | Gọi API bên thứ ba không hỗ trợ CORS |
| PostMessage | An toàn cho iframe | Chỉ cho window/iframe | Giao tiếp giữa các window/iframe |
9. Best Practices bảo mật khi cấu hình CORS#
1. Không dùng wildcard * trong production#
// ❌ Nguy hiểm
Access-Control-Allow-Origin: *
// ✓ An toàn
Access-Control-Allow-Origin: https://myapp.com
2. Whitelist origin cụ thể#
const allowedOrigins = ['https://myapp.com', 'https://admin.myapp.com'];
app.use(cors({
origin: (origin, callback) => {
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
3. Chỉ cho phép các method cần thiết#
// ❌ Dư thừa
methods: '*'
// ✓ Cụ thể
methods: ['GET', 'POST']
4. Validate Origin trên server-side#
CORS là cơ chế phía trình duyệt, không phải firewall. Luôn validate authentication/authorization độc lập — CORS không thay thế cho API security.
5. Dùng credentials: true cẩn thận#
Chỉ bật khi thực sự cần gửi cookie/token qua cross-origin requests, và luôn chỉ định origin cụ thể.
6. Xử lý preflight hiệu quả#
Set Access-Control-Max-Age hợp lý để giảm số lượng preflight requests, giảm tải cho server và tăng tốc độ tải trang.
10. Tổng kết#
| Khái niệm | Giải thích |
|---|---|
| CORS | Cơ chế cho phép cross-origin requests một cách an toàn |
| Same-Origin Policy | Chính sách mặc định chặn cross-origin requests |
| Preflight Request | Request OPTIONS kiểm tra quyền trước khi gửi request thật |
| Access-Control-Allow-Origin | Header quan trọng nhất — xác định origin được phép |
Key takeaways:#
- CORS là cơ chế bảo mật của trình duyệt, không phải của server
- Server quyết định cho phép origin nào thông qua CORS headers
- Preflight request là cách trình duyệt “xin phép” trước các request phức tạp
- Không dùng
*trong production — luôn chỉ định origin cụ thể - CORS không thay thế authentication — luôn bảo vệ API bằng token/session
Tài liệu tham khảo: