Vấn đềCác trình duyệt hiện đại rất khắt khe về bảo mật. Khi frontend của bạn cố gắng gửi một yêu cầu "không đơn giản" (non-simple request)—chẳng hạn như một POST với thân hàm (body) dạng application/json hoặc bất kỳ yêu cầu nào có header Authorization—trình duyệt sẽ không gửi nó đi ngay lập tức. Thay vào đó, nó sẽ gửi một yêu cầu OPTIONS trước. Đây chính là bước kiểm tra "preflight".
Trình duyệt về cơ bản đang hỏi: "Bạn có đồng ý để tôi gửi dữ liệu này không?" Nếu máy chủ phản hồi bằng bất kỳ mã trạng thái nào khác ngoài thành công (thường là 200 hoặc 204), trình duyệt sẽ ngắt kết nối. Nó thậm chí sẽ không cố gắng gửi dữ liệu thực tế của bạn. Đây là lý do tại sao bạn thấy lỗi "does not have HTTP ok status".
Nguyên nhân gốc rễ- Thiếu xử lý OPTIONS: Các route ở backend của bạn chỉ cho phép GET hoặc POST. Khi yêu cầu OPTIONS gửi đến máy chủ, nó sẽ trả về lỗi 405 Method Not Allowed.- Sự can thiệp của Auth Middleware: Hầu hết các API yêu cầu một JWT hoặc API key. Trình duyệt không bao gồm các thông tin xác thực này trong yêu cầu preflight. Nếu logic xác thực của bạn chạy quá sớm, nó sẽ trả về 401 Unauthorized vì thiếu token.- Thiếu cấu hình CORS toàn cục: Máy chủ thiếu một chính sách chung để xử lý các kiểm tra preflight trên tất cả các endpoint.## Khắc phục lỗi (Theo từng Framework)### 1. Node.js với ExpressTrong Express, gói cors là giải pháp tiêu chuẩn. Tuy nhiên, vị trí đặt mã là yếu tố quyết định. Bạn phải khởi tạo nó trước các route và quan trọng hơn là trước bất kỳ logic xác thực nào.
const express = require('express');
const cors = require('cors');
const app = express();
// 1. Bật CORS cho tất cả các route ngay lập tức
app.use(cors());
// 2. Xử lý OPTIONS một cách rõ ràng nếu cần thiết
app.options('*', cors());
// 3. Auth PHẢI nằm sau CORS
app.use(myAuthMiddleware);
app.post('/api/data', (req, res) => {
res.json({ message: 'Success' });
});
2. Cấu hình NginxXử lý preflight ở cấp độ Nginx thường nhanh hơn so với việc để yêu cầu đi tới mã ứng dụng. Hãy sử dụng khối mã này để chặn các yêu cầu OPTIONS và trả về trạng thái 204 ngay lập tức.
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin';
add_header 'Access-Control-Max-Age' 1728000; # Cache trong 20 ngày
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://backend_server;
}
3. Python FlaskĐối với các ứng dụng Flask, hãy sử dụng tiện ích mở rộng flask-cors. Hãy đảm bảo bao bọc đối tượng app của bạn sớm trong quá trình thiết lập.
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Lệnh này bật CORS cho tất cả các route và method
CORS(app)
@app.route("/api/data", methods=["POST", "OPTIONS"])
def handle_data():
return {"status": "ok"}
4. Spring Boot (Java)Spring Boot cho phép bạn định nghĩa một cấu hình toàn cục. Điều này đảm bảo rằng phương thức OPTIONS được cho phép ngay cả khi endpoint đó thường yêu cầu các quyền cụ thể.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
}
Thủ phạm "ẩn mình": Authentication MiddlewareMã của bạn vẫn bị lỗi ngay cả sau khi đã thêm hỗ trợ CORS? Vấn đề có khả năng nằm ở thứ tự thực hiện. Nếu máy chủ của bạn kiểm tra login token trước khi kiểm tra CORS, yêu cầu preflight sẽ luôn thất bại.
Các trình duyệt tiêu chuẩn không gửi header Authorization trong quá trình preflight OPTIONS. Nếu mã máy chủ của bạn trông giống như ví dụ dưới đây, nó sẽ bị lỗi:
app.use(verifyJWT); // ❌ Dòng này kiểm tra token và thất bại
app.use(cors()); // ❌ Dòng này không bao giờ được thực hiện tới
Hàm verifyJWT thấy một yêu cầu OPTIONS không có token và trả về 401 Unauthorized. Trình duyệt sau đó sẽ báo lỗi. Để khắc phục điều này, hãy luôn di chuyển middleware CORS lên trên cùng trong ngăn xếp (stack) của bạn.
Xác minhĐừng chỉ dựa vào trình duyệt để kiểm tra bản sửa lỗi của bạn. Hãy sử dụng curl để mô phỏng một yêu cầu preflight từ terminal. Điều này giúp tách biệt các vấn đề của backend khỏi các lỗi mã frontend.
curl -v -X OPTIONS http://localhost:3000/api/data \
-H "Access-Control-Request-Method: POST" \
-H "Origin: http://localhost:8080"
Kiểm tra hai điều sau trong kết quả đầu ra:
- Mã trạng thái HTTP phải là
200 OKhoặc204 No Content.- Các header phải bao gồmAccess-Control-Allow-Origin.## Phòng ngừa- Ưu tiên: Đặt CORS làm middleware đầu tiên trong logic ứng dụng của bạn.- Kiểm thử CI/CD: Thêm một trường hợp kiểm thử (test case) vào pipeline của bạn để gửi ping cụ thể đến API bằng phương thứcOPTIONS.- Giám sát nhật ký (Log): Tìm kiếm các mã trạng thái 401 hoặc 405 trong log máy chủ của bạn. Nếu chúng xuất hiện cùng với các yêu cầuOPTIONS, thứ tự middleware của bạn đang bị sai.

