Bạn đã bao giờ gặp lỗi CORS policy error khi gọi API từ một domain khác chưa? Bạn viết code hoàn hảo, API chạy tốt, nhưng trình duyệt lại từ chối phản hồi chỉ vì lý do "chính sách bảo mật"!

Đây là một vấn đề rất phổ biến khi làm việc với các ứng dụng web hiện đại. Đặc biệt nếu bạn đang phát triển ứng dụng frontend (React, Vue, Angular) gọi API từ một backend khác, hoặc xây dựng hệ thống microservices, bạn chắc chắn sẽ gặp lỗi CORS ít nhất một lần.

Vậy CORS là gì? Tại sao trình duyệt lại chặn yêu cầu giữa các domain khác nhau? Và làm sao để khắc phục lỗi CORS đúng cách mà không làm ảnh hưởng đến bảo mật hệ thống?

Hãy cùng khám phá trong bài viết này! 🚀

1. Giới thiệu về CORS

CORS (Cross-Origin Resource Sharing - Chia sẻ tài nguyên giữa các nguồn gốc khác nhau) là một cơ chế bảo mật của trình duyệt nhằm ngăn chặn các trang web khác nhau chia sẻ tài nguyên với nhau nếu không được phép.

Ví dụ: Khi một trang web chạy trên domain A (https://domain-a.com) muốn gọi API từ domain B (https://api.domain-b.com), trình duyệt sẽ chặn yêu cầu này nếu domain B không cho phép.

Tại sao lại có CORS?

  • Để ngăn chặn các cuộc tấn công bảo mật, chẳng hạn như Cross-Site Request Forgery (CSRF).
  • Để bảo vệ dữ liệu người dùng không bị đánh cắp bởi các trang web không đáng tin cậy.

2. CORS hoạt động như thế nào?

Trình duyệt kiểm tra CORS dựa trên HTTP headers mà server gửi về. Nếu server cho phép yêu cầu từ một nguồn khác, trình duyệt sẽ chấp nhận phản hồi, ngược lại sẽ chặn yêu cầu.

Quy trình kiểm tra CORS

  1. Trình duyệt gửi yêu cầu đến server kèm theo Origin header (nguồn gốc của yêu cầu).
  2. Server kiểm tra Origin và quyết định có cho phép truy cập hay không.
  3. Nếu được phép, server trả về Access-Control-Allow-Origin.
  4. Nếu không được phép, trình duyệt chặn yêu cầu và hiển thị lỗi CORS.

Ví dụ yêu cầu từ trình duyệt:

shell
GET /data HTTP/1.1
Host: api.domain-b.com
Origin: https://domain-a.com

Nếu server chấp nhận, nó sẽ trả về:

shell
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://domain-a.com

Ngược lại, nếu server không hỗ trợ CORS, trình duyệt sẽ chặn yêu cầu.

3. Các loại yêu cầu CORS

CORS được chia thành 3 loại chính:

3.1. Simple Requests (Yêu cầu đơn giản)

Nếu một request thỏa mãn các điều kiện sau, trình duyệt gửi request trực tiếp mà không cần kiểm tra trước:

  • Chỉ sử dụng các phương thức: GET, POST, HEAD
  • Không chứa headers tùy chỉnh (Authorization, Content-Type: application/json)
  • Không có XMLHttpRequest.withCredentials = true

Ví dụ: Một request hợp lệ

js
fetch("https://api.domain-b.com/data", {
  method: "GET",
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

3.2. Preflight Requests (Yêu cầu kiểm tra trước)

Nếu request có:

  • Các phương thức không chuẩn (PUT, DELETE, PATCH, OPTIONS)
  • Headers tùy chỉnh (Authorization, X-Token, Content-Type: application/json)
  • XMLHttpRequest.withCredentials = true

Trình duyệt sẽ gửi yêu cầu kiểm tra trước (OPTIONS request) trước khi gửi yêu cầu chính.

Ví dụ: Request bằng PUT

js
fetch("https://api.domain-b.com/data", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer my-token"
  },
  body: JSON.stringify({ name: "CodeTutHub" })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

Trình duyệt sẽ gửi một request OPTIONS trước:

shell
OPTIONS /data HTTP/1.1
Host: api.domain-b.com
Origin: https://domain-a.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type

Server phải trả về:

shell
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://domain-a.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type

3.3. Credentialed Requests (Yêu cầu có xác thực)

Nếu bạn muốn gửi cookie hoặc thông tin xác thực (Authorization token), bạn cần:

  • credentials: 'include' trong JavaScript.
  • Access-Control-Allow-Credentials: true trong server.

Ví dụ:

js
fetch("https://api.domain-b.com/protected", {
  method: "GET",
  credentials: "include"
})

Server phải phản hồi:

shell
Access-Control-Allow-Origin: https://domain-a.com
Access-Control-Allow-Credentials: true

Lưu ý:Access-Control-Allow-Origin: * không thể dùng khi có Access-Control-Allow-Credentials: true.

4. Cách khắc phục lỗi CORS

4.1. Cấu hình CORS trên server

Nếu bạn là admin của server, hãy thêm headers vào response.

Cấu hình trong PHP

shell
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");

Cấu hình trong Laravel

Sử dụng middleware trong app/Http/Middleware/Cors.php:

php
public function handle($request, Closure $next)
{
    $response = $next($request);
    $response->header('Access-Control-Allow-Origin', '*')
             ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
             ->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return $response;
}

Đăng ký middleware trong Kernel.php:

php
protected $middleware = [
    \App\Http\Middleware\Cors::class,
];

Cấu hình trong Express.js (Node.js)

js
const cors = require('cors');
app.use(cors({ origin: 'https://domain-a.com', credentials: true }));

4.2. Dùng Proxy Server

Nếu bạn không có quyền chỉnh server API, hãy sử dụng proxy để bypass CORS.

Dùng Nginx Reverse Proxy:

shell
location /api/ {
    proxy_pass https://api.domain-b.com/;
    add_header Access-Control-Allow-Origin *;
}

Hoặc dùng CORS Proxy miễn phí như:

js
fetch("https://cors-anywhere.herokuapp.com/https://api.domain-b.com/data")

5. Các lỗi thường gặp và cách xử lý

LỗiNguyên nhânCách khắc phục
CORS policy errorServer không gửi Access-Control-Allow-OriginCấu hình lại server hoặc dùng proxy
Preflight request blockedServer không cho phép phương thức PUT, DELETEThêm Access-Control-Allow-Methods trên server
Credential request blockedKhông có Access-Control-Allow-Credentials: trueCấu hình lại server, không dùng * cho Access-Control-Allow-Origin

6. Kết luận

🔹 CORS là cơ chế bảo mật quan trọng để ngăn chặn việc chia sẻ tài nguyên trái phép giữa các domain.
🔹 Nếu gặp lỗi CORS policy, bạn cần cấu hình lại server hoặc dùng proxy.
🔹 Hãy tối ưu hóa CORS để tránh bị chặn khi tích hợp API từ nguồn khác.

🚀 Hy vọng bài viết này giúp bạn hiểu rõ về CORS! Nếu bạn có câu hỏi, hãy bình luận trên CodeTutHub.com! 🚀