CORS là gì? Hiểu và xử lý lỗi CORS trong lập trình web

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Chắc hẳn bạn đã từng gặp thông báo đỏ lòm trong console: "blocked by CORS policy". Rất nhiều người mới phản ứng bằng cách tìm một đoạn mã dán vào cho hết lỗi mà không hiểu nguyên nhân. Bài này giải thích CORS là gì, tại sao nó tồn tại, và cách xử lý đúng.

Same-origin policy: gốc rễ của vấn đề

Một origin gồm ba phần: giao thức, tên miền và cổng. Ví dụ https://app.example.com và https://api.example.com là hai origin khác nhau, http://localhost:3000 và http://localhost:5173 cũng vậy.

Trình duyệt áp dụng chính sách cùng nguồn gốc (same-origin policy): mã JavaScript chạy ở origin này mặc định không được đọc phản hồi từ origin khác. Đây là biện pháp bảo vệ quan trọng. Nếu không, một trang web độc hại bạn vừa mở có thể dùng phiên đăng nhập của bạn để đọc dữ liệu từ ngân hàng hay email trong tab khác.

CORS là gì?

CORS (Cross-Origin Resource Sharing) là cơ chế cho phép máy chủ nới lỏng có kiểm soát chính sách trên. Máy chủ khai báo qua header HTTP rằng origin nào được phép đọc phản hồi. Hai điều cần ghi nhớ:

  • CORS là quy tắc do trình duyệt thực thi. Công cụ như Postman hay curl, hoặc máy chủ gọi máy chủ, không bị chặn kiểu này.
  • Cách sửa nằm ở máy chủ (bên cung cấp API), không phải ở mã fetch của frontend.

Luồng hoạt động cơ bản

Trang ở https://app.example.com gọi API ở https://api.example.com. Trình duyệt tự thêm header Origin vào yêu cầu. Nếu máy chủ trả về:

Access-Control-Allow-Origin: https://app.example.com

thì trình duyệt cho phép mã JavaScript đọc phản hồi. Nếu header thiếu hoặc không khớp, phản hồi bị chặn, và bạn thấy lỗi CORS. Lưu ý yêu cầu thực ra vẫn đến được máy chủ, chỉ là kết quả bị giấu khỏi mã của trang.

Preflight: yêu cầu thăm dò OPTIONS

Với yêu cầu "không đơn giản", chẳng hạn dùng method như PUT hoặc DELETE, hoặc có header tùy chỉnh như Authorization, hoặc Content-Type: application/json, trình duyệt sẽ gửi trước một yêu cầu OPTIONS để hỏi máy chủ có cho phép không:

OPTIONS /api/bai-viet
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization

Máy chủ phải trả lời với các header cho phép:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600

Chỉ khi preflight thành công, trình duyệt mới gửi yêu cầu thật. Rất nhiều lỗi CORS thực ra là do máy chủ không xử lý đúng yêu cầu OPTIONS (ví dụ trả về 404 hoặc 401).

Ví dụ cấu hình phía máy chủ

Với Express (Node.js), có thể dùng gói cors:

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
  origin: ['https://app.example.com', 'http://localhost:5173'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

Các framework khác (Laravel, Django, Spring, ASP.NET...) đều có middleware hoặc cấu hình tương tự. Hãy khai báo danh sách origin cụ thể thay vì để mở hoàn toàn.

Khi gửi cookie: credentials

Nếu frontend cần gửi cookie sang origin khác, phải đặt credentials: 'include' trong fetch, và máy chủ phải trả Access-Control-Allow-Credentials: true. Khi đó không được dùng dấu sao * cho Access-Control-Allow-Origin, mà phải chỉ rõ origin. Cookie còn chịu ảnh hưởng của thuộc tính SameSite và Secure.

Những cách "chữa cháy" nên tránh

  • Đặt Access-Control-Allow-Origin: * cho mọi API: chỉ chấp nhận được với dữ liệu thật sự công khai. Với API có xác thực, hãy giới hạn origin.
  • Tắt bảo mật của trình duyệt hoặc dùng tiện ích chặn CORS: chỉ che lỗi trên máy bạn, người dùng thật vẫn bị lỗi.
  • Dùng proxy công cộng của bên thứ ba: dữ liệu của bạn đi qua máy chủ lạ, không an toàn.

Giải pháp thay thế hợp lý

  1. Cấu hình CORS đúng trên máy chủ API, giải pháp chuẩn nhất.
  2. Dùng proxy phát triển: Vite, webpack dev server cho phép chuyển tiếp /api sang máy chủ backend, nên trình duyệt thấy cùng origin trong lúc phát triển.
  3. Dùng reverse proxy khi triển khai (như Nginx) để frontend và API cùng một tên miền.
  4. Gọi API từ phía máy chủ: ví dụ Route Handler trong Next.js làm trung gian, vừa tránh CORS vừa giấu được khóa bí mật.

Cách gỡ lỗi

  1. Đọc kỹ thông báo lỗi: nó thường nói rõ thiếu header nào.
  2. Mở tab Network, xem yêu cầu OPTIONS và phản hồi của nó, kiểm tra header trả về.
  3. So sánh chính xác origin trong lỗi với danh sách cho phép (giao thức, tên miền, cổng đều phải khớp, kể cả dấu gạch chéo cuối thì không được có).
  4. Đảm bảo máy chủ không trả về lỗi 500 hoặc chuyển hướng ở bước preflight.

Kết luận

CORS không phải là lỗi để "vượt qua" mà là một lớp bảo vệ. Hiểu rằng nó do trình duyệt thực thi và máy chủ quyết định cho phép giúp bạn sửa đúng chỗ: cấu hình header trên API, chỉ định origin cụ thể và xử lý tốt yêu cầu preflight.

Để xây dựng đầy đủ cả frontend và backend và hiểu cách chúng giao tiếp an toàn, bạn có thể xem các khóa học trên codebem.com.

author_photo
Nguyễn Đình Cường

0 comment