Khi tích hợp Zalo API, hệ thống đăng nhập hoặc một API bên ngoài vào website, lập trình viên có thể gặp thông báo Has been blocked by CORS policy trong Console của trình duyệt. Request đã được tạo từ JavaScript nhưng trình duyệt không cho ứng dụng đọc response vì máy chủ đích chưa chấp nhận nguồn gọi hiện tại.
Đây không phải lỗi riêng của Zalo và cũng không chỉ là lỗi JavaScript. CORS là cơ chế bảo mật của trình duyệt nhằm hạn chế một website tự ý truy cập dữ liệu từ nguồn khác. Muốn xử lý đúng, cần xác định frontend chạy ở đâu, API thuộc domain nào, server trả về header gì và request có phát sinh preflight hay không.
Nội dung chính
Has been blocked by CORS policy là lỗi gì?
Thông báo Has been blocked by CORS policy xuất hiện khi trình duyệt phát hiện request khác nguồn nhưng response không đáp ứng quy tắc Cross-Origin Resource Sharing. Website chạy tại một origin, còn API được gọi nằm tại origin khác. Nếu máy chủ API không cho phép origin của website, trình duyệt sẽ chặn việc truy cập response.
Một origin được xác định bởi giao thức, tên miền và cổng. Chỉ cần một thành phần khác nhau thì request có thể bị xem là cross-origin. Ví dụ:
https://app.example.comvàhttps://api.example.comhttp://example.comvàhttps://example.comhttps://example.com:443vàhttps://example.com:8443
CORS không phải lúc nào cũng ngăn request rời khỏi trình duyệt. API có thể vẫn nhận request và ghi log, nhưng trình duyệt không cho frontend đọc kết quả. Vì vậy, thấy server xử lý thành công chưa có nghĩa cấu hình CORS đã đúng.
CORS hoạt động như thế nào?

Khi JavaScript dùng fetch, XMLHttpRequest hoặc thư viện HTTP để gọi API khác origin, trình duyệt kiểm tra response có Access-Control-Allow-Origin phù hợp hay không. Nếu header thiếu, sai hoặc không khớp origin hiện tại, response sẽ bị chặn.
Với request dùng PUT, PATCH, DELETE, header tùy chỉnh hoặc Content-Type: application/json, trình duyệt thường gửi một request OPTIONS trước. Đây là preflight, dùng để hỏi server xem method và header dự kiến có được phép hay không.
Vì sao gặp lỗi Has been blocked by CORS policy?
Nguyên nhân phổ biến nhất là API chưa cấu hình CORS hoặc chỉ cho phép một số domain. Trong quá trình tích hợp Zalo API, lỗi còn xuất hiện khi frontend gọi trực tiếp endpoint cần bảo mật, trong khi endpoint đó được thiết kế cho kết nối server-to-server.
Server thiếu Access-Control-Allow-Origin
Nếu response không có Access-Control-Allow-Origin, trình duyệt không biết server có chấp nhận website hiện tại hay không. Header hợp lệ có thể là:
Access-Control-Allow-Origin: https://app.example.com
Với tài nguyên công khai, không gửi cookie hoặc thông tin xác thực, server có thể dùng:
Access-Control-Allow-Origin: *
Không nên dùng dấu * cho mọi API chỉ để xóa lỗi. Cấu hình này mở quyền truy cập quá rộng và không dùng được với request có credential.
Preflight OPTIONS không được xử lý

Request chính có thể dùng POST, nhưng lỗi thực tế nằm ở request OPTIONS. Nếu web server, reverse proxy hoặc firewall trả về 404, 405 hay 500 cho OPTIONS, trình duyệt sẽ dừng trước khi gửi request chính.
Server cần phản hồi preflight bằng các header phù hợp:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Response OPTIONS có thể trả về mã 204 và không cần body. Header phải khớp với method và các header frontend chuẩn bị sử dụng.
Origin không khớp cấu hình
Server cho phép https://example.com nhưng ứng dụng chạy tại https://www.example.com vẫn có thể gặp Has been blocked by CORS policy. Hai địa chỉ này là hai origin khác nhau.
Tương tự, http://localhost:3000 không giống http://127.0.0.1:3000; cổng 3000 cũng khác 5173. Khi kiểm tra, hãy lấy chính xác giá trị header Origin mà trình duyệt gửi lên.
Credential và wildcard xung đột
Khi frontend gửi cookie, session hoặc đặt credentials: "include", server phải trả về origin cụ thể, không thể dùng:
Access-Control-Allow-Origin: *
Server cũng phải thêm:
Access-Control-Allow-Credentials: true
Nếu thiếu header này hoặc vẫn dùng wildcard, trình duyệt sẽ chặn response.
Redirect, proxy hoặc CDN làm mất header
CORS có thể đúng trong ứng dụng nhưng sai sau khi đi qua Nginx, Apache, CDN hoặc API Gateway. Redirect từ HTTP sang HTTPS, domain cũ sang domain mới hoặc endpoint không có dấu gạch chéo sang endpoint có dấu gạch chéo cũng có thể tạo response trung gian thiếu header.
Hãy kiểm tra các mã 301, 302, 307, 308, 401, 403 và 500 trong tab Network. Header CORS nên xuất hiện cả trên response lỗi nếu frontend cần đọc nội dung lỗi.
Cách kiểm tra lỗi CORS chính xác

Mở Developer Tools, chuyển sang tab Network và lọc theo endpoint. Kiểm tra cả request chính lẫn request OPTIONS. Console chỉ báo lỗi tổng quát, còn Network cho thấy status code, request header, response header và redirect.
Các điểm cần đối chiếu gồm:
- Giá trị
Origintrình duyệt gửi lên Access-Control-Allow-Origintrong response- Method có nằm trong
Access-Control-Allow-Methods Authorization,Content-Typehoặc header tùy chỉnh có được cho phép- Request có gửi cookie hoặc credential
- Preflight trả về status code nào
- Response có bị chuyển sang domain khác
Có thể dùng curl để mô phỏng preflight:
curl -i -X OPTIONS “https://api.example.com/resource” -H “Origin: https://app.example.com” -H “Access-Control-Request-Method: POST” -H “Access-Control-Request-Headers: Content-Type, Authorization”
Nếu response thiếu header cần thiết, vấn đề nằm ở server hoặc proxy. Nếu curl đúng nhưng trình duyệt vẫn lỗi, hãy kiểm tra cache, service worker, redirect và origin thực tế.
Cách fix bằng header phía server
Giải pháp bền vững nhất là cấu hình CORS tại server sở hữu API. Frontend không thể tự thêm Access-Control-Allow-Origin vào response của máy chủ khác. Thêm header này vào request cũng không có tác dụng vì trình duyệt kiểm tra header response.
Ví dụ với Node.js và Express
Có thể dùng middleware cors và chỉ cho phép các domain tin cậy:
import express from “express”;
import cors from “cors”;
const app = express();
const allowedOrigins = [
“https://app.example.com”,
“http://localhost:5173”
];
app.use(cors({
origin(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
return callback(null, true);
}
return callback(new Error(“Origin không được phép”));
},
methods: [“GET”, “POST”, “PUT”, “DELETE”, “OPTIONS”],
allowedHeaders: [“Content-Type”, “Authorization”],
credentials: true
}));
app.use(express.json());
Danh sách origin nên lấy từ biến môi trường để tách development, staging và production. Không nên phản chiếu mọi giá trị Origin về response khi chưa kiểm tra whitelist.
Ví dụ với PHP
PHP có thể kiểm tra origin rồi trả về header tương ứng:
try {
const response = await fetch(“https://api.example.com/profile”, {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
“Authorization”: `Bearer ${process.env.ACCESS_TOKEN}`
},
body: JSON.stringify(req.body)
});
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
res.status(502).json({ message: “Không thể kết nối API đích” });
}
});
Proxy vừa khắc phục **Has been blocked by CORS policy**, vừa hỗ trợ kiểm soát rate limit, log và lỗi. Tuy nhiên, proxy phải xác thực người dùng, giới hạn endpoint được gọi và không được biến thành open proxy nhận URL tùy ý.
Tiện ích tắt CORS hoặc proxy công cộng chỉ nên dùng để chẩn đoán tạm thời. Chúng không phải giải pháp production và có thể làm lộ token hoặc dữ liệu người dùng.
## Lưu ý bảo mật khi cấu hình CORS cho Zalo API
CORS không thay thế xác thực và phân quyền. API cho phép origin cụ thể vẫn phải kiểm tra access token, quyền người dùng và dữ liệu đầu vào. Cấu hình quá rộng có thể tạo điều kiện để website lạ gửi request từ trình duyệt người dùng.
Khi tích hợp Zalo API hoặc dữ liệu khách hàng, nên:
– Chỉ cho phép đúng domain frontend
– Tách whitelist cho local, staging và production
– Không đặt app secret hoặc token dài hạn trong frontend
– Không dùng `Access-Control-Allow-Origin: *` với dữ liệu riêng tư
– Chỉ cho phép method và header thật sự cần
– Xác thực request và kiểm tra quyền tại backend
– Thiết lập rate limit, log và cảnh báo bất thường
– Trả header CORS trên cả response thành công và thất bại
Cần phân biệt lỗi CORS với lỗi API. Endpoint có thể trả 401 vì token hết hạn, nhưng response 401 thiếu header CORS khiến trình duyệt chỉ hiển thị lỗi CORS. Hãy bảo đảm response 4xx và 5xx cũng có header phù hợp, sau đó xử lý vấn đề xác thực riêng.
## Quy trình xử lý Has been blocked by CORS policy hiệu quả
Khi gặp **Has been blocked by CORS policy**, không nên thử ngẫu nhiên nhiều header hoặc tắt bảo mật của trình duyệt. Trước hết, xác định request nên gọi từ frontend hay phải chuyển sang backend. Tiếp theo, kiểm tra preflight, origin, method, credential, redirect và từng lớp proxy.
Nếu sở hữu API, hãy cấu hình whitelist tại server, xử lý `OPTIONS`, cho phép đúng method và header, đồng thời thêm `Vary: Origin` khi hỗ trợ nhiều nguồn. Nếu không sở hữu API, hãy dùng backend proxy an toàn thay vì cố vượt CORS từ trình duyệt. khám phá thêm tại zalo chat web.
Xử lý đúng lỗi này không chỉ giúp request hoạt động mà còn giữ ranh giới bảo mật giữa frontend, backend và API bên ngoài. Với ứng dụng tích hợp Zalo API, gọi API nhạy cảm từ server thường an toàn, dễ kiểm soát và ổn định hơn việc đưa toàn bộ logic kết nối vào trình duyệt.

Vương Minh – người dẫn dắt và đứng sau sự thành công của ZALO Web cùng với đội ngũ kĩ sư ưu tú nhất. Với khát vọng tạo ra một sản phẩm công nghệ “Make in Vietnam” đủ sức cạnh tranh sòng phẳng với các đối thủ quốc tế, ông Khải cùng các cộng sự tại Zalo Group đã không ngừng cải tiến để Zalo không chỉ là app nhắn tin mà còn là một hệ sinh thái làm việc đa nền tảng. Zalo Web chính là minh chứng cho triết lý đó: tinh gọn, tốc độ và thấu hiểu sâu sắc thói quen của người dùng Việt.
