CORS Là Gì? Hướng Dẫn Toàn Diện & Bảo Mật Tối Ưu Năm 2026
Lê Đình Đài

CORS Là Gì? Hướng Dẫn Khắc Phục Lỗi CORS Policy Triệt Để 2026
Xin chào các lập trình viên fullstack và dev web!
Trong thế giới web hiện đại năm 2026, khi microservices, API-driven architecture và edge computing đang bùng nổ, CORS (Cross-Origin Resource Sharing) không chỉ là một cơ chế kỹ thuật mà còn là "lá chắn bảo mật" quan trọng nhất ngăn chặn các cuộc tấn công cross-domain.
Bạn đã từng mất hàng giờ debug lỗi "CORS policy blocked" chỉ vì một header sai? Hay lo lắng cấu hình CORS lỏng lẻo sẽ biến API của bạn thành mục tiêu tấn công?
Bài viết này được cập nhật dựa trên OWASP Top 10 2025 (với Security Misconfiguration lên vị trí #2 và CORS misconfiguration là ví dụ điển hình trong Broken Access Control), browser stricter enforcement năm 2026, và best practices mới nhất. Chúng ta sẽ đi sâu từ cơ bản đến nâng cao, kèm code thực tế, case study thực tế, giúp bạn cấu hình CORS an toàn, hiệu quả mà không ảnh hưởng performance.
Hãy cùng dinhdai.tech biến CORS từ "kẻ thù" thành "đồng minh" mạnh mẽ nhé!
1. CORS Là Gì? Khái Niệm Cơ Bản

Origin là gì?
Một origin được định nghĩa bởi bộ ba: protocol (http/https), domain (ví dụ: example.com), và port (thường là 80 hoặc 443). Ví dụ:
- https://frontend.example.com
- https://api.backend.com (khác origin)
- http://localhost:3000 (khác origin so với production)
Tại sao CORS cần thiết?
Trong thế giới web ngày nay, các ứng dụng thường được xây dựng theo mô hình micro-frontends, Single Page Applications (SPA) (React, Vue, Angular), hoặc tích hợp nhiều dịch vụ từ các domain khác nhau (ví dụ: frontend trên Vercel gọi API trên AWS, hoặc dùng Google Maps API, Stripe payment...). Không có CORS, trình duyệt sẽ block các request cross-origin theo chính sách Same-Origin Policy (SOP) mặc định, dẫn đến lỗi và không thể tích hợp mượt mà.
Định nghĩa CORS (Cross-Origin Resource Sharing) chi tiết
CORS là chuẩn của W3C (Web Hypertext Application Technology Working Group), cho phép browser và server "thương lượng" qua các HTTP headers đặc biệt để quyết định xem một request cross-origin có được phép thực hiện hay không.
Quá trình diễn ra như sau:
- Trình duyệt gửi request cross-origin (ví dụ: GET từ frontend đến API khác domain).
- Nếu là request "simple" (GET/HEAD/POST với header an toàn), browser gửi luôn.
- Nếu là request "preflight" (OPTIONS method), browser gửi trước một request kiểm tra để hỏi server: "Có cho phép origin này không?".
- Server trả về headers như
Access-Control-Allow-Origin,Access-Control-Allow-Methods, v.v. - Nếu hợp lệ → browser cho phép request thật. Nếu không → block và báo lỗi console như: "No 'Access-Control-Allow-Origin' header is present..."
Không có CORS đúng đắn, browser sẽ block request để bảo vệ người dùng khỏi các tấn công như Cross-Site Request Forgery (CSRF) hoặc đọc dữ liệu từ domain độc hại.
So sánh CORS với Same-Origin Policy (SOP)
Same-Origin Policy (SOP) là quy tắc bảo mật cốt lõi của trình duyệt từ những năm 1990s:
- Chỉ cho phép JavaScript trên một trang web truy cập tài nguyên (XMLHttpRequest/Fetch, DOM, cookies...) của cùng origin.
- Mục đích: Ngăn chặn website độc hại đọc dữ liệu từ ngân hàng online hoặc email của bạn.
CORS là cơ chế "nới lỏng có kiểm soát" SOP:
- Server (không phải client) quyết định origin nào được phép truy cập.
- Mang lại sự linh hoạt cho web hiện đại (API public, third-party integration), nhưng đòi hỏi cấu hình chính xác để tránh lỗ hổng.
Vai trò của CORS trong bảo mật web hiện đại (năm 2026)
Trong năm 2026, với sự bùng nổ của API-first architecture, microservices, và API exposure công khai (hàng triệu API public trên internet), CORS đóng vai trò then chốt:
- Ngăn chặn data leak từ các request cross-origin trái phép.
- Hỗ trợ chống lại các tấn công như XSS (kết hợp với CSP) và CSRF (bằng cách kiểm soát origin).
Theo OWASP Top 10:2025 (phiên bản mới nhất):
- A01:2025 - Broken Access Control vẫn xếp hạng #1 (3.73% ứng dụng bị ảnh hưởng).
- A02:2025 - Security Misconfiguration nhảy vọt lên #2 (3.00% ứng dụng), trong đó CORS misconfiguration được liệt kê rõ ràng như một ví dụ điển hình (cùng với SSRF, token manipulation, v.v.).
Trong OWASP API Security Top 10 2023 (vẫn tham chiếu rộng rãi năm 2026), API8:2023 - Security Misconfiguration bao gồm "overly permissive CORS policies" như một rủi ro phổ biến.
Lợi ích và rủi ro khi sử dụng CORS năm 2026
Lợi ích (với ví dụ cụ thể):
- Hỗ trợ SPA mượt mà: Frontend React trên
https://app.mysite.comgọi API backend trênhttps://api.mysite.com→ người dùng trải nghiệm liền mạch mà không bị block. - Microservices & third-party integration: Ứng dụng e-commerce gọi payment gateway Stripe (từ domain khác) hoặc Google Analytics, Maps API → dễ dàng tích hợp mà vẫn an toàn.
- Public API exposure: Cho phép developer bên thứ ba (như mobile app hoặc partner) sử dụng API của bạn một cách có kiểm soát.
Rủi ro (và thống kê cập nhật):
Misconfiguration (đặc biệt dùng wildcard * hoặc Access-Control-Allow-Origin: * mà không kết hợp Access-Control-Allow-Credentials: false) là lỗ hổng rất phổ biến.
Theo các báo cáo bảo mật gần đây (2025):
- Khoảng 40% web applications có CORS settings bị misconfigured (theo security audits). Khi tính cả các trường hợp chưa phát hiện, con số có thể lên tới 60% APIs dễ bị khai thác (theo Medium/Coders Stop 2025 và các audit tương tự).
- 99% tổ chức báo cáo gặp vấn đề API security trong năm qua, trong đó security misconfiguration (bao gồm CORS) là nguyên nhân phổ biến dẫn đến data exposure (Salt Security & các report 2025).
- Nhiều breach lớn bắt nguồn từ permissive CORS, cho phép attacker từ domain độc hại đọc dữ liệu nhạy cảm (như token, user data).
CORS là "cánh cửa" linh hoạt nhưng phải khóa chặt – cấu hình sai có thể biến API của bạn thành "cửa mở" cho attacker.
2. Origin Là Gì Và Cross-Origin Request

Origin được định nghĩa như thế nào?
Origin bao gồm ba thành phần chính (theo chuẩn WHATWG và MDN Web Docs):
- Scheme/Protocol (giao thức): http hoặc https (khác nhau là khác origin).
- Host/Domain (tên miền, bao gồm subdomain): ví dụ example.com, api.example.com, localhost.
- Port (cổng): số port (nếu không chỉ định, browser dùng port mặc định: 80 cho http, 443 cho https).
Port mặc định không hiển thị trong URL, nhưng vẫn được tính – nếu port khác (dù ngầm định), thì khác origin.
Bảng so sánh ví dụ (cùng hay khác origin so với origin gốc: https://www.example.com)
| URL ví dụ | Protocol | Domain/Host | Port | Kết luận |
|---|---|---|---|---|
| https://www.example.com/path | https | www.example.com | 443 | Cùng origin |
| https://example.com/path | https | example.com | 443 | Khác (subdomain khác) |
| http://www.example.com | http | www.example.com | 80 | Khác (protocol khác) |
| https://www.example.com:8080 | https | www.example.com | 8080 | Khác (port khác) |
| https://api.example.com | https | api.example.com | 443 | Khác (subdomain khác) |
| https://localhost:3000 | https | localhost | 3000 | Khác (domain khác) |
Lưu ý: localhost và 127.0.0.1 cũng coi là khác nhau dù cùng máy!
Cross-Origin Request Là Gì? So Sánh Trực Tiếp
Same-origin request (cùng origin):
- Trang web tại
https://frontend.example.comgọi API tạihttps://frontend.example.com/api/users(cùng protocol, domain, port). - Browser cho phép tự do: JavaScript đọc response, truy cập DOM, cookies, v.v. mà không cần CORS.
Cross-origin request (khác origin):
- Trang web tại
https://frontend.example.com(origin A) gọi API tạihttps://api.example.com(origin B). - Browser sẽ block request nếu không có CORS headers từ server (ví dụ: Access-Control-Allow-Origin).
- Lý do: Bảo vệ user khỏi script độc hại đọc dữ liệu từ domain khác (như ngân hàng, email).
Các Ví Dụ Về Request Cross-Origin Phổ Biến (Thực Tế 2026)
Dưới đây là những tình huống bạn gặp hàng ngày trong phát triển web:
- Frontend SPA gọi backend riêng domain
- Trang web React/Vue/Angular chạy tại
http://localhost:3000(hoặchttps://app.mysite.com). - Gọi API backend tại
https://api.mysite.comhoặchttps://backend.aws.com. → Cross-origin vì domain/port khác → cần CORS để fetch() hoặc Axios thành công. - Nhúng Google Fonts (hoặc CDN fonts/scripts)
- Trang web của bạn tại
https://mysite.comdùng@font-facetừhttps://fonts.googleapis.comvà tải font file từhttps://fonts.gstatic.com. → Origin khác (Google domain) → browser yêu cầu CORS header từ Google (Google đã hỗ trợ sẵn, nhưng nếu proxy hoặc mạng block, có thể lỗi). - Tích hợp third-party services
- Ứng dụng e-commerce tại
https://shop.vngọi payment gateway Stripe tạihttps://api.stripe.com. - Hoặc nhúng Google Analytics script từ
https://www.googletagmanager.com, hoặc Maps API từhttps://maps.googleapis.com. → Cross-origin → cần server bên thứ ba hỗ trợ CORS (hầu hết đều có). - Mobile WebView hoặc hybrid app
- WebView trong app Android/iOS (origin từ file:// hoặc custom scheme) gọi API server thực tế. → Thường cross-origin → cần cấu hình CORS cho phép origin đặc biệt.
Khi Nào Cần Sử Dụng CORS Trong Phát Triển Web?
- Bất kỳ khi frontend và backend nằm trên domain khác (micro-frontends, separate deployment).
- Integrate external/third-party APIs (Stripe, OpenAI, AWS S3, Google APIs, Mapbox, Facebook SDK...).
- Development phase (localhost:3000 gọi localhost:8080 hoặc production API).
- Production với subdomain riêng (app.example.com gọi api.example.com).
Nếu không cấu hình CORS → lỗi console phổ biến:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Tác Động Của CORS Đến Fetch API Và XMLHttpRequest (XHR)
- Fetch API, Axios, XMLHttpRequest đều bị ràng buộc nghiêm ngặt bởi CORS (browser-side enforcement).
- Năm 2026, các browser hiện đại (Chrome 142+, Firefox 145+, Edge) enforce stricter hơn:
- Tự động block nếu thiếu headers đúng (Access-Control-Allow-Origin).
- Preflight (OPTIONS) yêu cầu nghiêm ngặt hơn cho private network access (PNA).
- Có thêm fix cho DNS rebinding bypass CORS (như CVE-2025-8036 ở Firefox trước 141).
- Không còn "relax" cho các trường hợp cũ; misconfig dễ dẫn đến block hoàn toàn.
Kết quả: Nếu server không trả Access-Control-Allow-Origin phù hợp → request fail, dù server xử lý bình thường (không thấy request ở log server nếu preflight fail).
3. Cách Hoạt Động Của CORS

Trình duyệt luôn gửi header Origin trong mọi cross-origin request (ví dụ: Origin: https://frontend.example.com). Server kiểm tra và trả về các header Access-Control-* để "cho phép" hoặc từ chối.
Có hai loại request chính: Simple Request (đơn giản, không cần kiểm tra trước) và Preflight Request (phức tạp, cần gửi OPTIONS trước để hỏi ý kiến server).
Simple Request Và Preflight Request Khác Nhau Ra Sao?
Simple Request (không preflight – gửi trực tiếp):
- Chỉ áp dụng cho các request "an toàn" và không gây side-effect lớn.
- Điều kiện (theo chuẩn Fetch spec):
- Method: GET, HEAD, hoặc POST.
- Headers chỉ dùng các header "safelist" (như Accept, Accept-Language, Content-Language, Content-Type với giá trị: application/x-www-form-urlencoded, multipart/form-data, hoặc text/plain).
- Không có custom headers (như Authorization, X-API-Key).
- Không dùng ReadableStream hoặc upload progress events.
Ví dụ: Một GET đơn giản hoặc POST form data → browser gửi ngay request thật, kèm Origin header. Server chỉ cần trả Access-Control-Allow-Origin phù hợp để browser cho đọc response.
Preflight Request (có kiểm tra trước – OPTIONS):
- Dùng cho request "không an toàn" hoặc có thể gây thay đổi dữ liệu (side-effect).
- Trigger khi:
- Method không phải GET/HEAD/POST (ví dụ: PUT, DELETE, PATCH).
- Content-Type không safelist (ví dụ: application/json).
- Có custom headers (như Authorization, X-Requested-With).
Quy trình:
- Browser gửi request OPTIONS (preflight) với các header đặc biệt:
Access-Control-Request-Method: method thật (ví dụ: PUT).Access-Control-Request-Headers: danh sách custom headers.Origin: origin của trang web.- Server trả response với các header cho phép:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- (tùy chọn) Access-Control-Max-Age
- Nếu hợp lệ → browser gửi request thật. Nếu không → block và báo lỗi console.
Lợi ích: Giảm rủi ro bằng cách hỏi trước server trước khi gửi request nguy hiểm (như DELETE dữ liệu).
Các HTTP Headers Quan Trọng Trong CORS
Dưới đây là các header chính mà server cần trả về (response headers):
- Access-Control-Allow-Origin:
Chức năng: Chỉ định origin nào được phép đọc response.
Giá trị phổ biến:
*: Cho phép mọi origin (chỉ dùng khi không có credentials).https://example.com: Chỉ cho phép origin cụ thể (an toàn nhất).
Lưu ý bảo mật: Không được dùng * khi có credentials (cookies/auth) – browser block ngay (quy tắc nghiêm ngặt từ spec, và browser 2026 enforce chặt hơn). Nếu hỗ trợ nhiều origin → server phải đọc Origin request header và trả về chính giá trị đó, kèm Vary: Origin để cache đúng.
- Access-Control-Allow-Methods
Chức năng: Liệt kê các HTTP methods được phép (dùng trong preflight).
Ví dụ: GET, POST, PUT, DELETE, OPTIONS
Lưu ý: Không hỗ trợ wildcard như * cho methods.
- Access-Control-Allow-Headers
Chức năng: Liệt kê các request headers custom được phép (dùng trong preflight).
Ví dụ: Content-Type, Authorization, X-API-Key
Lưu ý: Phải liệt kê rõ ràng tất cả header mà client gửi trong Access-Control-Request-Headers; không dùng * khi có credentials.
- Access-Control-Allow-Credentials
Chức năng: Cho phép request bao gồm credentials (cookies, HTTP auth, client certificates).
Giá trị: Chỉ true (hoặc không có).
Lưu ý bảo mật quan trọng: Khi set true, Access-Control-Allow-Origin hải là origin cụ thể (không được dùng *). Browser 2026 block nghiêm ngặt nếu vi phạm → tránh lộ dữ liệu nhạy cảm.
- Access-Control-Max-Age
Chức năng: Thời gian cache kết quả preflight (giây), giảm số lần gửi OPTIONS lặp lại.
Ví dụ: 86400 (24 giờ).
Lưu ý: Browser có thể giới hạn max nội bộ (thường 24h), nếu không set thì default thấp (khoảng 5-10 giây).
CORS Với Credentials (Cookies, Authentication)
Khi dùng fetch hoặc XMLHttpRequest với credentials: 'include' (hoặc withCredentials = true):
- Request sẽ tự động gửi cookies/auth headers.
- Server phải trả:
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: https://your-frontend.com(không được*!).
Nếu server trả * + credentials → browser block response và báo lỗi kiểu:
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'.
Best practice năm 2026:
- Tránh wildcard
*trong production, đặc biệt với credentials. - Dùng origin cụ thể và dynamic check Origin header.
- Kết hợp
Vary: Originđể cache đúng.
4. Lỗi CORS Phổ Biến Và Nguyên Nhân

Lỗi cors là gì: No 'Access-Control-Allow-Origin' header
Server không trả header phù hợp → browser block response.
Nguyên nhân lỗi cors policy blocked
Origin không match whitelist, preflight fail, hoặc misconfiguration credentials.
Lỗi khi sử dụng credentials mà không cấu hình đúng
Dùng * với credentials → browser reject ngay lập tức (stricter enforcement 2025+).
Cách debug lỗi CORS trong console browser 2026
DevTools → Network tab → Xem request OPTIONS/main → Chi tiết headers và error message. Sử dụng extension như CORS Unblock chỉ cho dev.
5. Cách Cấu Hình CORS Trên Server

Các bước cơ bản để cấu hình:
- Xác định các origin được phép (trusted origins) – ưu tiên cụ thể thay vì
*. - Quyết định có hỗ trợ credentials (cookies, auth) không – nếu có, phải dùng origin cụ thể +
Access-Control-Allow-Credentials: true. - Cho phép methods và headers cần thiết (đặc biệt cho preflight).
- Cache preflight bằng
Access-Control-Max-Ageđể tối ưu performance. - Áp dụng global hoặc per-route, tùy framework/server.
- Test với browser dev tools (Network tab) và công cụ như curl.
Best practices chung (2026):
- Tránh
*ở production, đặc biệt khi có credentials. - Echo lại
Originrequest header nếu dynamic (và thêmVary: Origin). - Giới hạn methods/headers chỉ những gì cần thiết.
- Xử lý OPTIONS riêng nếu cần (preflight).
Cấu Hình CORS Cơ Bản Với Access-Control-Allow-Origin
Server phải trả header Access-Control-Allow-Origin cho mọi response (bao gồm lỗi 4xx/5xx nếu muốn).
Ví dụ an toàn:
Access-Control-Allow-Origin: https://frontend.example.com
Hoặc dynamic: đọc req.headers.origin → nếu trong whitelist thì trả chính origin đó.
Bật CORS Trên Node.js/Express (Sử Dụng cors Middleware – Khuyến nghị)
Cài đặt: npm install cors
Ví dụ cấu hình an toàn với whitelist + credentials:
JavaScript
const express = require('express');
const cors = require('cors');
const app = express();
const whitelist = [
'https://app1.example.com',
'https://app2.example.com',
'http://localhost:3000' // cho dev
];
app.use(cors({
origin: function (origin, callback) {
// Cho phép request không có origin (như curl, Postman)
if (!origin) return callback(null, true);
if (whitelist.includes(origin)) {
callback(null, true); // hoặc callback(null, origin) để echo
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true, // nếu cần cookie/auth
maxAge: 86400 // cache preflight 24h
}));
// Hoặc đơn giản hơn nếu không cần dynamic:
// app.use(cors({ origin: whitelist, credentials: true, maxAge: 86400 }));
app.listen(5000, () => console.log('Server running'));
Lưu ý: Middleware cors tự xử lý OPTIONS preflight → không cần route riêng.
Cấu Hình CORS Trong Spring Boot/Java (Global – Khuyến nghị)
Sử dụng CorsConfigurationSource để cấu hình global (áp dụng cho tất cả endpoints).
Ví dụ an toàn (Spring Boot 3.x+):
Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.List;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("https://*.example.com", "http://localhost:3000")); // regex cho subdomain
// Hoặc setAllowedOrigins(List.of("https://app.example.com")) cho cụ thể
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
config.setAllowedHeaders(List.of("*")); // hoặc cụ thể: "Content-Type", "Authorization"
config.setExposedHeaders(List.of("Authorization")); // nếu cần expose header
config.setAllowCredentials(true); // cho cookie/auth
config.setMaxAge(86400L); // 24h cache preflight
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); // áp dụng cho mọi path
return new CorsFilter(source);
}
}
Nếu dùng Spring Security → thêm .cors() trong SecurityFilterChain:
Java
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
Alternative đơn giản (per-controller): @CrossOrigin(origins = "https://frontend.com", allowCredentials = "true")
Xử Lý CORS Trên Nginx/Apache Và Cloud Services
Nginx (reverse proxy – khuyến nghị cho production, offload từ backend):
nginx
server {
listen 443 ssl;
server_name api.example.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400 always;
return 204;
}
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
# Proxy đến backend (Spring/Node)
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Apache (trong .htaccess hoặc virtual host):
apache
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://frontend.example.com"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Max-Age "86400"
# Cho OPTIONS preflight
<If "%{REQUEST_METHOD} == 'OPTIONS'">
Header set Access-Control-Allow-Origin "https://frontend.example.com"
Header always unset Access-Control-Allow-Origin
Header always set Access-Control-Allow-Origin "https://frontend.example.com"
</If>
</IfModule>
Cloud services (AWS API Gateway, Cloudflare, Vercel...): Thường có UI hoặc config JSON để set allowed origins, methods, headers – ưu tiên dùng để tránh hardcode.
Bắt đầu với whitelist cụ thể + credentials nếu cần. Test kỹ với preflight (OPTIONS) và credentials. Nếu dùng wildcard * chỉ cho public API không auth.
6. CORS Với Các Framework Và API Hiện Đại

Dưới đây là cách cấu hình CORS phổ biến trên các framework/backend hiện đại:
1. Express.js (Node.js) – Sử dụng middleware cors
Đây là cách phổ biến nhất cho backend JavaScript/TypeScript.
JavaScript
const express = require('express');
const cors = require('cors');
const app = express();
// Cấu hình an toàn với whitelist
const allowedOrigins = [
'https://app.example.com',
'http://localhost:5173', // Vite dev server
'https://*.vercel.app' // cho preview branches
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin) || origin.endsWith('.vercel.app')) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
}));
// Hoặc đơn giản nếu không cần dynamic:
app.use(cors({ origin: allowedOrigins, credentials: true }));
Middleware cors tự động xử lý OPTIONS preflight, rất tiện cho REST API.
2. Spring Boot (Java/Kotlin) – Sử dụng @CrossOrigin hoặc global config
- Per-controller (nhanh cho prototype):
Java
@RestController
@CrossOrigin(
origins = {"https://frontend.example.com", "http://localhost:3000"},
allowCredentials = "true",
maxAge = 3600
)
@RequestMapping("/api")
public class UserController { ... }
- Global (khuyến nghị production): Như phần trước (CorsConfigurationSource với regex cho subdomain).
3. Django (Python) – Sử dụng django-cors-headers
Cài đặt: pip install django-cors-headers
Thêm vào [settings.py](http://settings.py):
Python
INSTALLED_APPS = [
...,
"corsheaders",
]
MIDDLEWARE = [
...,
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
...
]
# Cấu hình an toàn
CORS_ALLOWED_ORIGINS = [
"https://app.example.com",
"http://localhost:3000",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
CORS_ALLOW_HEADERS = ["authorization", "content-type"]
Rất phù hợp cho REST API (Django REST Framework) hoặc GraphQL (Strawberry/Graphene).
CORS Trong Fetch API Và Axios (Frontend)
Client-side không "cấu hình" CORS (server quyết định), nhưng bạn cần set đúng mode và credentials:
JavaScript
// Fetch API
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // nếu cần cookie/auth
mode: 'cors' // mặc định, nhưng rõ ràng tốt hơn
})
.then(res => res.json())
.catch(err => console.error('CORS error:', err));
// Axios
axios.get('https://api.example.com/data', {
withCredentials: true, // tương đương credentials: 'include'
})
.catch(err => console.log('Blocked by CORS:', err.message));
Tích Hợp CORS Với React/Vue Frontend
- Development:
Vite (React/Vue): Thêm proxy trong vite.config.js để bypass CORS trong dev:
JavaScript
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080', // backend
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
→ Gọi /api/users → proxy đến backend mà không trigger CORS.
- Create React App (webpack):
proxy: "http://localhost:8080"trongpackage.json. - Production: Không dùng proxy! Backend phải xử lý CORS nghiêm ngặt (whitelist origin của frontend deploy).
CORS Trong Microservices Và API Gateway (2026)
Trong kiến trúc microservices:
- AWS API Gateway: Tạo Resource → Enable CORS (UI) hoặc dùng Lambda Authorizer để validate
Originheader động. - Kong / Traefik / Istio: Thêm plugin CORS → set allowed origins, methods, headers. Kong hỗ trợ Lua script để dynamic check (ví dụ: regex subdomain).
- GraphQL (Apollo Server, Hasura): Apollo Server dùng
corsmiddleware giống Express; Hasura có built-in CORS config trong admin UI.
Sử Dụng CORS Proxy Tạm Thời Cho Development
Tại sao cần proxy trong dev?
Frontend (localhost:3000/5173) và backend (localhost:8080) chạy trên origin khác → browser block request → lỗi CORS. Proxy giúp "giả lập" cùng origin bằng cách chuyển tiếp request qua frontend server.
Công cụ phổ biến:
- Vite proxy (như ví dụ trên) – tốt nhất cho React/Vue hiện đại.
- Webpack Dev Server proxy – cho CRA.
- cors-anywhere: Gọi
https://cors-anywhere.herokuapp.com/https://api.example.com– nhanh cho test, nhưng chậm và không ổn định. - Tự tạo proxy đơn giản (Node.js + http-proxy):
JavaScript
const httpProxy = require('http-proxy');
httpProxy.createProxyServer({ target: 'http://localhost:8080' }).listen(3001);
→ Gọi http://localhost:3001/api từ frontend.
Cảnh báo quan trọng:
Chỉ dùng proxy trong development! Production tuyệt đối tránh vì:
- Proxy có thể bị lạm dụng (open proxy = lỗ hổng bảo mật).
- Không kiểm soát được origin → dễ lộ dữ liệu.
- Tăng latency và phụ thuộc bên thứ ba.
Ưu tiên cấu hình CORS đúng ở backend (whitelist + credentials). Dùng proxy chỉ dev. Với microservices/API Gateway, tận dụng built-in CORS để scale an toàn.
7. Bảo Mật CORS: Rủi Ro Và Misconfiguration

Theo các báo cáo bảo mật gần đây (Salt Security API Security Report 2025, Akamai State of the Internet 2025), khoảng 40-60% các API public/private có ít nhất một misconfiguration CORS, dẫn đến rò rỉ dữ liệu nhạy cảm, token hijacking hoặc hỗ trợ tấn công cross-site.
Dưới đây là các lỗi misconfiguration phổ biến nhất, hậu quả và cách khắc phục:
1. Dùng Wildcard * Cho Access-Control-Allow-Origin Khi Có Credentials
Lỗi phổ biến:
http
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Hậu quả nghiêm trọng:
- Trình duyệt block response theo spec (và enforce chặt hơn từ Chrome 120+ và Firefox 2025+).
- Nhưng nếu server vẫn trả
*+ credentials → attacker có thể exploit bằng cách: - Tạo trang độc hại (attacker.com) → dùng JavaScript gửi request đến victim API với credentials của victim (nếu victim đã login).
- Vì
*cho phép mọi origin đọc response → attacker đọc được dữ liệu nhạy cảm (email, token, balance tài khoản). - Đây là nguyên nhân chính gây data exposure trong nhiều breach API lớn năm 2024-2025.
Khắc phục:
- Tuyệt đối không kết hợp
*vớicredentials: true. - Phải dùng origin cụ thể:
Access-Control-Allow-Origin: https://trusted-frontend.com - Nếu hỗ trợ nhiều origin → đọc
Originheader và echo lại chính xác (dynamic whitelist).
2. Whitelist Origin Không Chính Xác Hoặc Quá Lỏng (Null Origin, Regex Sai)
Lỗi phổ biến:
- Cho phép
nullorigin (do một số browser cũ hoặc extension):Access-Control-Allow-Origin: null - Regex sai:
https://*.example.com→ attacker dùnghttps://evil-example.com(subdomain giả mạo). - Cho phép
http://thay vì chỉhttps:// (downgrade attack).
Hậu quả:
- Attacker tạo subdomain giả hoặc dùng
nullorigin (từ sandboxed iframe) → đọc dữ liệu từ API. - Hỗ trợ tấn công CSRF kết hợp CORS: attacker gửi request từ domain khác, đọc response nếu misconfig.
Khắc phục:
- Sử dụng whitelist cứng (array cụ thể) hoặc regex an toàn:
- Spring Boot:
config.setAllowedOriginPatterns(List.of("https://app\\.example\\.com", "https://preview-.*\\.vercel\\.app")) - Node.js: Kiểm tra strict
origin.endsWith('.example.com') && origin.startsWith('https://') - Không cho phép
null,http://, hoặc wildcard không kiểm soát.
3. Không Xử Lý Preflight OPTIONS Đúng Cách
Lỗi phổ biến:
- Không trả
Access-Control-Allow-MethodshoặcAccess-Control-Allow-Headersđầy đủ → preflight fail. - Hoặc trả
*cho methods/headers mà không validate.
Hậu quả:
- Request phức tạp (PUT/DELETE, custom headers như Authorization) bị block → nhưng nếu attacker bypass (dùng simple request), vẫn lộ dữ liệu.
- Một số misconfig cho phép attacker gửi request nguy hiểm mà không preflight.
Khắc phục:
- Luôn liệt kê rõ:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
4. Không Set Access-Control-Max-Age → Preflight Lặp Lại Quá Nhiều
Hậu quả:
- Performance kém, dễ bị DoS (attacker spam OPTIONS).
- Không phải lỗ hổng trực tiếp nhưng làm lộ cấu hình CORS qua traffic.
Khắc phục:
- Set
Access-Control-Max-Age: 86400(24h) hoặc cao hơn nếu ổn định.
Tấn Công Dựa Trên CORS Misconfiguration (Ví Dụ Thực Tế)
- CORS + CSRF: Attacker tạo trang độc hại → dùng form POST cross-origin với credentials → nếu CORS cho phép, server xử lý request (thay đổi mật khẩu, chuyển tiền).
- CORS + XSS: Kết hợp XSS để đọc response từ domain khác (nếu
*hoặc origin sai). - Token Theft: Attacker đọc JWT từ API mà victim đã auth → dùng token đó ở nơi khác.
Best Practices Bảo Mật CORS Năm 2026 (Defense in Depth)
- Ưu tiên whitelist cụ thể hoặc regex chặt chẽ (Spring
allowedOriginPatterns, Node dynamic check). - Kết hợp với các biện pháp khác:
- CSP (Content-Security-Policy): giới hạn nguồn script/fetch.
- SameSite cookies:
StricthoặcLaxđể chống CSRF. - JWT với short-lived token + refresh token.
- Rate limiting và WAF (Cloudflare, AWS WAF) để phát hiện origin lạ.
- Token binding hoặc DPoP cho OAuth.
- Test tự động: Dùng công cụ như OWASP ZAP, Burp Suite, hoặc cors-scanner để scan misconfig.
- Monitor: Theo dõi log CORS errors và Origin header lạ.
CORS misconfiguration không phải "lỗi nhỏ" – nó có thể biến API của bạn thành "cửa mở" cho attacker. Luôn cấu hình cụ thể, an toàn, và kết hợp defense in depth.
8. Best Practices CORS Năm 2026

Mục tiêu: An toàn cao nhất + hiệu năng tốt + dễ bảo trì.
1. Luôn chỉ định origin cụ thể – Tuyệt đối tránh wildcard * (Ưu tiên cao nhất)
- Lý do:
*cho phép mọi domain đọc response → nguy cơ lộ dữ liệu nhạy cảm cực cao nếu kết hợp credentials (cookies, auth). Browser 2026 block nghiêm ngặt* + credentials, và attacker dễ exploit nếu bạn vô tình cho phép. - Cách làm đúng:
- Whitelist cứng:
Access-Control-Allow-Origin: https://app.example.com - Hoặc dynamic: Đọc
Originrequest header → kiểm tra trong whitelist → echo lại chính origin đó (thêmVary: Originđể cache đúng). - Ví dụ Node.js:
JavaScript
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
2. Chỉ cho phép HTTP methods và headers thực sự cần thiết
- Lý do: Giảm bề mặt tấn công. Nếu cho phép
DELETEhoặcPUTkhông cần thiết → attacker có thể thử xóa/sửa dữ liệu qua preflight bypass hoặc CSRF. - Best practice:
Access-Control-Allow-Methods: GET, POST, OPTIONS(nếu không cần PUT/DELETE).Access-Control-Allow-Headers: Content-Type, Authorization(chỉ những gì client dùng).- Không dùng
*cho methods/headers – liệt kê rõ ràng để tránh lộ quyền không mong muốn.
3. Tối ưu preflight cache với Access-Control-Max-Age
- Preflight là gì?: Với request phức tạp (PUT/DELETE, custom headers), browser gửi OPTIONS trước để hỏi server "Có cho phép không?". Đây là request riêng biệt, tốn tài nguyên server.
- Lý do cache quan trọng: Giảm số lần gửi OPTIONS lặp lại → cải thiện tốc độ (đặc biệt SPA gọi API thường xuyên) và giảm load server.
- Cách làm:
http
Access-Control-Max-Age: 86400 // 24 giờ
- Ý nghĩa: Browser cache kết quả preflight trong 24h → chỉ gửi OPTIONS lại sau 24h hoặc khi cấu hình thay đổi.
- Cảnh báo: Không set quá lâu (ví dụ 30 ngày) nếu bạn hay thay đổi whitelist/methods → browser sẽ dùng cache cũ, dẫn đến lỗi hoặc lỗ hổng tạm thời.
4. Hỗ trợ credentials chỉ khi thực sự cần – Và luôn validate origin nghiêm ngặt
- Lý do: Khi dùng
credentials: 'include'(cookie, auth), server phải trảAccess-Control-Allow-Credentials: truevà origin cụ thể (không*). - Best practice:
- Chỉ bật credentials cho endpoint cần (ví dụ login, user profile).
- Server validate
Originheader động (không tin tưởng client). - Kết hợp SameSite=Strict/Lax cho cookies để chống CSRF.
5. Phân biệt rõ ràng giữa Development và Production
- Development: Linh hoạt hơn – dùng proxy (Vite/Webpack), cho phép
http://localhost:*, nullorigin (cho một số extension). - Production: Strict nhất –
- Whitelist chỉ domain production (https://).
- Không cho
http://,localhost, hoặcnull. - Monitor log Origin header lạ → alert nếu phát hiện tấn công.
- Sử dụng WAF (Cloudflare, AWS WAF) để block origin không mong muốn.
6. Theo dõi và cập nhật theo chuẩn W3C & browser mới nhất
- Lý do: Browser 2026 enforce stricter:
- Block wildcard
*+ credentials hoàn toàn. - Enhanced preflight scrutiny (Private Network Access – PNA).
- DNS rebinding protection (CVE liên quan CORS).
- Hành động:
- Theo dõi MDN Web Docs, WHATWG Fetch spec, Chrome/Firefox release notes.
- Test định kỳ với công cụ như OWASP ZAP, CORS-Scanner.
- Cập nhật framework (Spring Boot 3.4+, Express cors 2.8+).
7. Áp dụng Defense in Depth – Không chỉ dựa vào CORS
- Kết hợp:
- CSP (Content-Security-Policy) để giới hạn nguồn fetch/script.
- JWT với short-lived + refresh token.
- Rate limiting + token binding.
- Logging & monitoring Origin lạ.
Tóm tắt ưu tiên nhanh (2026):
- Không dùng
*(đặc biệt với credentials). - Whitelist cụ thể + dynamic validate.
- Giới hạn methods/headers.
- Cache preflight hợp lý (86400s).
- Strict production – linh hoạt dev.
- Theo dõi browser changes.
Áp dụng đầy đủ các practice này, bạn sẽ giảm đáng kể rủi ro CORS misconfiguration – một trong những lỗ hổng phổ biến nhất hiện nay.
9. Ví Dụ Thực Tế Và Case Study CORS

1. Ví dụ thực tế: SPA (React) gặp lỗi CORS khi gọi API backend
Tình huống phổ biến:
Frontend React deploy trên Vercel (https://my-app.vercel.app) gọi API Node.js trên Render (https://my-api.onrender.com).
Browser console báo lỗi:
text
Access to fetch at 'https://my-api.onrender.com/users' from origin 'https://my-app.vercel.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Nguyên nhân: Backend chưa set CORS headers.
Cách fix từng bước (Node.js/Express):
- Cài middleware:
npm install cors - Cấu hình dynamic whitelist + logging (an toàn cho production):
JavaScript
const express = require('express');
const cors = require('cors');
const morgan = require('morgan'); // để log
const app = express();
app.use(morgan('dev')); // log request
const allowedOrigins = [
'https://my-app.vercel.app',
'https://*.vercel.app', // cho preview branches
'http://localhost:5173' // dev
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.some(o => origin === o || origin.endsWith('.vercel.app'))) {
console.log(`CORS allowed for origin: ${origin}`);
callback(null, true);
} else {
console.error(`CORS blocked for origin: ${origin}`);
callback(new Error('Not allowed by CORS policy'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'], // nếu cần expose custom header
maxAge: 86400
}));
// Route ví dụ
app.get('/users', (req, res) => {
res.json([{ id: 1, name: 'Ngọc Linh' }]);
});
app.listen(3000, () => console.log('API running'));
Sau khi deploy lại → request thành công, console không còn lỗi CORS.
2. Case Study: Lỗ hổng bảo mật do CORS misconfiguration (dựa trên báo cáo thực tế 2025)
Tình huống từ báo cáo Salt Security API Security Report 2025:
Một công ty fintech để Access-Control-Allow-Origin: * + Access-Control-Allow-Credentials: true trên endpoint /api/user/profile (lỗi nghiêm trọng).
Hậu quả:
- Attacker tạo trang web độc hại (
evil.com). - Khi victim (đã login vào app) truy cập evil.com, JavaScript gửi request cross-origin đến
/api/user/profilevới credentials (cookie session). - Vì
*cho phép mọi origin đọc response → attacker đọc được thông tin cá nhân, token, balance tài khoản. - Breach ảnh hưởng hàng nghìn user, lộ dữ liệu nhạy cảm → công ty bị phạt GDPR và mất lòng tin.
Bài học rút ra:
- Không bao giờ dùng
*khi có credentials. - Luôn validate
Originheader server-side (dynamic echo). - Kết hợp SameSite=Lax cho cookies và short-lived JWT.
3. Áp dụng CORS với Third-Party API (Stripe, Google)
Third-party như Stripe hay Google Maps enforce strict CORS:
- Chỉ cho phép origin cụ thể (đã đăng ký trong dashboard).
- Không hỗ trợ
*cho credentials.
Học theo:
- Khi build public API → yêu cầu developer đăng ký origin (như Stripe).
- Ví dụ: Trong dashboard, thêm
https://my-app.com→ server chỉ trảAccess-Control-Allow-Origincho origin đã đăng ký.
4. Debug và Fix CORS Trong Môi Trường Cloud (AWS, Vercel) Năm 2026
AWS API Gateway (REST API) – Cấu hình CORS dễ dàng qua console:
- Vào API Gateway → Chọn API → Resources → Enable CORS (nút trên thanh Actions).
- Tick các method cần (GET, POST, OPTIONS...).
- Set:
- Access-Control-Allow-Origin:
'https://my-frontend.vercel.app'(hoặc'*'nếu public no-credentials). - Access-Control-Allow-Headers:
'Content-Type, Authorization'. - Access-Control-Allow-Methods:
'GET, POST, OPTIONS'. - Access-Control-Allow-Credentials:
'true'(nếu cần). - Deploy API lại (Stages → Deploy).
AWS S3 (cho static hosting hoặc CORS cho bucket):
- Vào S3 Bucket → Permissions → CORS configuration → Edit:
JSON
[
{
"AllowedOrigins": ["https://my-app.vercel.app"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
Vercel (Serverless Functions hoặc Edge Middleware):
- Trong
vercel.json(cho toàn project):
JSON
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "https://my-frontend.vercel.app" },
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" },
{ "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" },
{ "key": "Access-Control-Allow-Credentials", "value": "true" }
]
}
]
}
Tích hợp WAF (Web Application Firewall):
- Cloudflare WAF hoặc AWS WAF → tạo rule block request nếu
Originheader không khớp whitelist (ví dụ: regex^https://.*\.example\.com$). - Lợi ích: Bảo vệ thêm lớp ngoài backend, detect và block origin lạ ngay từ edge → giảm load server và phát hiện tấn công sớm.
Debug tips nhanh:
- Mở DevTools → Network → Chọn request → Headers → Check
Originvà response CORS headers. - Nếu preflight fail → xem OPTIONS request status (thường 403/204).
- Dùng
curl -H "Origin: https://test.com" -X OPTIONS https://api.comđể test server-side.
Áp dụng các ví dụ trên, bạn sẽ xử lý được hầu hết vấn đề CORS trong dự án fullstack thực tế.
❓ Câu hỏi thường gặp
8 câu hỏi
Cấu hình CORS với credentials: true
Client (Fetch/Axios):
JavaScript
fetch('https://api.example.com', {
credentials: 'include' // gửi cookie/auth
});
Server (Node/Express):
JavaScript
app.use(cors({
origin: 'https://frontend.example.com', // PHẢI cụ thể, KHÔNG dùng '*'
credentials: true
}));
Server (Spring Boot):
Java
config.setAllowedOrigins(List.of("https://frontend.example.com"));
config.setAllowCredentials(true);
Quy tắc bắt buộc:
Client: credentials: 'include'
Server: Access-Control-Allow-Credentials: true + Access-Control-Allow-Origin:
Vi phạm → browser block ngay. Chỉ dùng khi cần cookie/session cross-origin.
Kết luận
CORS năm 2026 không còn là "cơ chế cũ kỹ" mà là yếu tố quyết định sự an toàn của toàn bộ hệ thống web/API. Với OWASP Top 10 2025 nhấn mạnh misconfiguration là top risk, việc cấu hình CORS đúng không chỉ giúp tránh lỗi phiền phức mà còn bảo vệ dữ liệu user khỏi các cuộc tấn công tinh vi.
Hãy áp dụng ngay: strict whitelist, validate server-side, cache preflight, và kết hợp multi-layer security. Dự án của bạn sẽ chạy mượt mà, an toàn hơn, và sẵn sàng scale trong kỷ nguyên API-first.
Cảm ơn bạn đã đọc hết! Nếu áp dụng thành công, chia sẻ kinh nghiệm bên dưới nhé. Chúc code vui và không còn sợ CORS!

Lê Đình Đài
- Kinh nghiệm 5 năm vận hành Shopee & TikTok Shop
- Xây shop thời trang nữ từ 0đ lên doanh thu 5 tỷ/tháng
Founder của dinhdai.tech - Nơi chia sẻ kiến thức, công cụ AI miễn phí và giải pháp tối ưu cho seller. Sứ mệnh của tôi là giúp mọi người kinh doanh hiệu quả hơn với công nghệ.