CORS là gì? Origin (scheme + host + port) giải thích đơn giản【2026】

(Cập nhật: 19 tháng 7, 2026 ) CORS Origin Same-Origin Policy Access-Control-Allow-Origin frontend API trình duyệt
Kết luận

CORS bắt đầu từ một câu hỏi: hai URL có cùng Origin không? Origin = scheme + host + port. Khác một phần → cross-origin → trình duyệt chặn đọc response trừ khi server cho phép qua CORS. Postman 200 không chứng minh gì. Hiểu Origin trước — rồi mới đọc CORS localhost hoặc fix production.

Sinh viên và freelancer Việt Nam hay gặp console đỏ blocked by CORS policy ngay buổi đầu gọi API. Phản ứng phổ biến: “React sai”, “Thêm mode: 'no-cors', hoặc “Cài extension tắt CORS”. Ba hướng đó đều lệch vì chưa hiểu Origin.

Bài này không dạy cấu hình Express/Vite chi tiết. Mục tiêu: nắm CORS là gì, Origin đơn giản, và biết khi nào chuyển sang bài fix local / production.

Bài viết này giúp bạn

  • Định nghĩa CORS và mối quan hệ với Same-Origin Policy
  • Phân tích Origin theo scheme / host / port (bảng so sánh)
  • Đọc đúng dòng lỗi console trước khi đổ tội framework
  • Phân biệt simple requestpreflight (OPTIONS) ở mức khái niệm
  • Checklist 5 phút + lộ trình đọc tiếp (local → production)

CORS là gì?

CORS (Cross-Origin Resource Sharing) là cơ chế để trang ở origin A được phép đọc response từ origin B khi server B đồng ý.

Trình duyệt mặc định áp Same-Origin Policy (SOP): script trên trang chỉ được đọc tự do tài nguyên cùng origin. Không có SOP, site độc hại có thể fetch API ngân hàng của bạn (khi bạn đang đăng nhập) và đọc JSON.

CORS không xóa SOP. Nó là cửa sổ ngoại lệ có kiểm soát: server trả header kiểu Access-Control-Allow-Origin → trình duyệt mới giao response cho JavaScript.

Hai sự thật hay bị nhầm
  1. Request vẫn có thể tới server. CORS chủ yếu chặn JS đọc response, không phải lúc nào cũng chặn packet đi. Network có thể thấy 200 nhưng Console vẫn đỏ.
  2. Sửa ở client không đủ. Header CORS phải đến từ server API (hoặc proxy làm request cùng origin với app).

Origin = scheme + host + port

Hai URL cùng origin chỉ khi cả ba khớp:

Thành phầnVí dụĐổi = origin khác?
Schemehttp vs https
Hostlocalhost vs 127.0.0.1 vs api.example.com
Port5173 vs 8080 vs 443 (https mặc định)

Path (/api/users) và query (?id=1) không thuộc Origin. https://app.com/ahttps://app.com/bcùng origin.

Bảng so sánh nhanh

Cùng origin hay không?
URL A (trang) URL B (API) Kết luận
https://shop.vn https://shop.vn/api Cùng origin — CORS không chặn đọc
https://shop.vn https://api.shop.vn Khác host — cần CORS hoặc proxy
http://localhost:5173 http://localhost:8080 Khác port — vẫn cross-origin
http://localhost:5173 http://127.0.0.1:5173 Khác host — localhost ≠ 127.0.0.1
http://app.local https://app.local Khác scheme — http ≠ https

Đây là lý do team outsourcing hay sốc: “Cùng máy, cùng localhost mà bị CORS?” — vì port khác = origin khác. Chi tiết proxy Vite/Next nằm ở CORS trên localhost.


Trình duyệt làm gì khi cross-origin?

Luồng rút gọn:

  1. Trang https://app.vn gọi fetch('https://api.vn/users').
  2. Trình duyệt gắn header Origin: https://app.vn.
  3. Server trả response. Nếu khôngAccess-Control-Allow-Origin phù hợp (hoặc không khớp), JS không đọc được body — Console báo CORS.
  4. Nếu có preflight (OPTIONS trước): server phải trả lời OPTIONS đúng, rồi mới đến GET/POST thật.

Simple vs preflight (đủ để nhận diện)

LoạiKhi nàoBạn thấy gì
SimpleGET/POST “đơn giản”, Content-Type kiểu formThường một request
Preflightapplication/json, header Authorization, method PUT/DELETE…OPTIONS rồi mới request thật

Thiếu header trên OPTIONS hoặc trên request thật đều fail. Đừng chỉ nhìn status của GET mà bỏ qua OPTIONS đỏ.

Đoạn lỗi mẫu — đọc hai giá trị

Access to fetch at 'http://localhost:8080/api/users'
from origin 'http://localhost:5173'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Ghi ngay:

  1. Origin trang: http://localhost:5173
  2. URL bị gọi: http://localhost:8080/api/users

So sánh scheme/host/port → bạn đã biết vì sao bị coi là cross-origin. Bước tiếp theo mới là sửa ở đâu.


CORS không phải lỗi React / Axios

Hiện tượngÝ nghĩa thật
Postman 200, Chrome đỏCORS chỉ enforce trên browser
mode: 'no-cors'Response opaque — JS gần như không đọc JSON được
Extension “Allow CORS”Chỉ máy bạn; teammate/staging vẫn fail
Access-Control-Allow-Origin: * + cookieThường không đi cùng credentials; dễ “chạy local rồi prod vỡ”
Quy tắc thực dụng

Thấy CORS → hỏi “Origin trang khác Origin API ở phần nào?” trước khi mở file React. 80% lần đầu, câu trả lời nằm ở port hoặc subdomain.


Case study ngắn

1) Sinh viên: frontend 5173, Spring Boot 8080

fetch('http://localhost:8080/api/hello') từ Vite → Console CORS. Postman gọi cùng URL → 200. Kết luận đúng: khác port. Hướng xử lý: proxy Vite hoặc cho phép origin http://localhost:5173 trên Spring — xem CORS localhost.

2) Freelancer: local ổn, staging fail

Local dùng proxy /api → cùng origin lúc dev. Staging gọi https://api.khach.com trực tiếp → không còn proxy. Lỗi “đột nhiên” trên staging là CORS production, không phải “React đổi version”. Đọc CORS Error production trước ngày demo.


Checklist 5 phút (trước khi Google lung tung)

  1. Chép đủ dòng Console (có from originfetch at)
  2. Viết Origin trang và Origin API cạnh nhau — khoanh chỗ khác
  3. Network: có OPTIONS đỏ không?
  4. Thử curl/Postman chỉ để xem status API — đừng kết luận “hết CORS”
  5. Không cài extension làm bước “fix chính thức”
  6. Chọn bài tiếp theo:

Khi debug response JSON đã qua CORS, có thể dùng JSON Formatter trên trình duyệt để đọc payload — công cụ không sửa CORS, chỉ giúp soi dữ liệu sau khi request đã thành công.


Lộ trình đọc tiếp

Bạn đang ở đâuĐọc tiếp
Mới gặp từ CORS / OriginBài này (xong)
Vite/Next + API local khác portCORS trên localhost
Local ổn, staging/prod đỏCORS Error khi lên production
Muốn sâu OPTIONS / preflightBài preflight (khi cần header phức tạp)

Tóm tắt

  • CORS = cách server cho phép trang origin khác đọc response.
  • Origin = scheme + host + port; path không tính.
  • localhost khác port vẫn là cross-origin.
  • Postman ≠ trình duyệt.
  • Sửa ở server hoặc proxy, không phải “tắt CORS trên Chrome”.
  • Hiểu Origin xong hãy vào bài local / production — đỡ mất nửa ngày copy header sai chỗ.