Trong những năm gần đây, WordPress Headless kết hợp với các framework hiện đại như Astro.js đang trở thành lựa chọn phổ biến cho developer và doanh nghiệp khi xây dựng website hiệu năng cao. Mô hình này tách rời hoàn toàn phần quản trị nội dung (WordPress) và phần hiển thị giao diện (frontend), giúp website linh hoạt hơn, dễ mở rộng và cải thiện trải nghiệm người dùng. Tuy nhiên, đi kèm với lợi ích đó là những thách thức không nhỏ về bảo mật.
Khác với WordPress truyền thống, khi triển khai theo hướng headless, hệ thống thường giao tiếp qua REST API hoặc GraphQL, mở ra nhiều bề mặt tấn công mới nếu không được cấu hình đúng cách. Việc bảo vệ dữ liệu, kiểm soát quyền truy cập API, cũng như đảm bảo an toàn cho backend WordPress là yếu tố bắt buộc, đặc biệt với các website doanh nghiệp hoặc sản phẩm có lượng truy cập lớn.
Trong bài viết trước, tôi đã chia sẻ tổng quan về trải nghiệm headless CMS, Astro.js và lý do vì sao mô hình này được developer đánh giá cao. Tiếp nối nội dung đó, bài viết này sẽ tập trung vào những kiến thức bảo mật căn bản khi xây dựng WordPress Headless với Astro.js. Bạn sẽ hiểu rõ các rủi ro thường gặp, nguyên tắc bảo mật quan trọng và cách tiếp cận đúng ngay từ đầu để tránh những sai lầm phổ biến khi triển khai thực tế.

Yes, tất nhiên là khi bạn dùng Headless thì trang web của bạn đã bảo mật hơn nhiều so với WordPress Dynamic!
Khi bạn dùng Headless WordPress thì trang web của bạn đã bảo mật hơn rất nhiều so với WordPress Dynamic truyền thống. Lý do đầu tiên nằm ở việc tách biệt frontend và backend. Với Headless, WordPress chỉ đóng vai trò quản lý nội dung, không trực tiếp hiển thị giao diện ra bên ngoài. Điều này giúp giảm đáng kể bề mặt tấn công từ hacker.
Trong mô hình WordPress Dynamic, các thành phần như theme, plugin, trang đăng nhập /wp-admin hay /wp-login.php đều công khai trên internet. Đây là mục tiêu phổ biến của các cuộc tấn công brute force, SQL injection hoặc khai thác lỗ hổng plugin. Ngược lại, Headless WordPress thường chỉ cung cấp dữ liệu thông qua REST API hoặc GraphQL, và các endpoint này có thể được giới hạn quyền truy cập, xác thực bằng token hoặc IP whitelist.

Ngoài ra, frontend của Headless thường được xây dựng bằng các công nghệ hiện đại như Next.js, Nuxt hoặc React, triển khai trên CDN hoặc static hosting. Điều này giúp giảm rủi ro tấn công trực tiếp vào server WordPress, đồng thời cải thiện tốc độ tải trang và khả năng mở rộng.
Tóm lại, Headless WordPress không chỉ mang lại hiệu suất cao hơn mà còn giúp nâng cao mức độ bảo mật tổng thể, đặc biệt phù hợp với các website lớn, dự án doanh nghiệp và hệ thống cần độ an toàn cao.
Bạn có trang web WordPress Headless + Astro.js và muốn kiểm tra xem web có bảo mật không, có bị đưa secret ra front end không, làm thế nào?
Việc kết hợp WordPress (Headless) với Astro.js là một hướng đi rất tốt về hiệu suất, nhưng nó cũng tạo ra một “vùng xám” về bảo mật nếu bạn không cẩn thận với cách truyền dữ liệu.
Dưới đây là các bước kiểm tra cụ thể để đảm bảo “bí mật” của bạn không bị lộ ra ngoài:
Kiểm tra biến môi trường (.env) trong Astro
Astro có cơ chế phân biệt rất rõ ràng giữa Server-side và Client-side.
- Quy tắc vàng: Các biến trong file
.envkhông có tiền tốPUBLIC_sẽ chỉ khả dụng trên server. Nếu bạn đặt tên làPUBLIC_WORDPRESS_TOKEN, nó sẽ bị đóng gói vào file JavaScript gửi xuống trình duyệt. - Cách kiểm tra:
- Mở code source, tìm kiếm tất cả các biến bắt đầu bằng
PUBLIC_. - Nếu bạn thấy các API Key “nhạy cảm” (như JWT Secret, Admin Email, hoặc Database URL) có tiền tố này, hãy xóa nó ngay.
- Mở code source, tìm kiếm tất cả các biến bắt đầu bằng
Kiểm tra Network Tab (Công cụ thực tế nhất)
Đây là cách nhanh nhất để xem “người dùng” thực sự thấy gì.
- Mở trang web của bạn trên trình duyệt.
- Nhấn
F12-> chọn thẻ Network. - Tải lại trang (
F5). - Trong ô tìm kiếm (Filter), gõ các từ khóa như:
wp-json,token,key, hoặc các đoạn text mà bạn nghi là Secret. - Nếu thấy: Một request XHR/Fetch trả về toàn bộ thông tin User hoặc cấu hình hệ thống, nghĩa là bạn đang gọi API trực tiếp từ Client thay vì thông qua Astro Server.
Kiểm tra quá trình Hydration
Astro nổi tiếng với kiến trúc “Islands”. Nếu bạn chuyển dữ liệu từ WordPress vào một Component (React, Vue, Svelte) và sử dụng directive client:load, Astro sẽ chuyển dữ liệu đó thành JSON để gửi xuống client.
- Rủi ro: Bạn fetch dữ liệu từ WP gồm 20 trường (trong đó có
user_email,hashed_password), nhưng chỉ hiển thịuser_namelên màn hình. Toàn bộ 20 trường đó vẫn nằm trong thẻ<script type="application/json">ẩn dưới HTML. - Cách khắc phục: Luôn “lọc” dữ liệu (mapping) trước khi truyền vào component. Chỉ lấy những gì cần hiển thị.
Các lỗ hổng bảo mật phổ biến (Checklist)
| Đối tượng | Nguy cơ | Cách xử lý |
| WP-JSON | Lộ danh sách User (/wp-json/wp/v2/users) | Sử dụng plugin bảo mật hoặc code để chặn truy cập công khai API này. |
| CORS | Domain lạ có thể gọi API của WP | Giới hạn Access-Control-Allow-Origin chỉ cho domain của Astro. |
| Astro Actions | Lộ logic xử lý server | Đảm bảo không console.log các secret bên trong các hàm xử lý dữ liệu. |
Công cụ quét tự động (Security Scanners)
Bạn có thể sử dụng các công cụ sau để quét “bề nổi” của trang web:
- Mozilla Observatory: Kiểm tra các Header bảo mật (HTTPS, CSP, HSTS).
- Gitleaks / TruffleHog: Chạy công cụ này trên thư mục code của bạn để xem bạn có lỡ tay
commitmã bí mật lên GitHub/GitLab không. - ZAP (OWASP): Quét các lỗ hổng bảo mật web phổ biến.
Lời khuyên: Đừng bao giờ gọi trực tiếp WordPress API từ trình duyệt bằng Token có quyền Admin. Luôn dùng Astro làm “người trung gian” (Middleware) để giấu đi sự hiện diện của Backend.
Mapping dữ liệu trong Astro để lọc bỏ các thông tin nhạy cảm trước khi gửi xuống Front-end
Việc viết hàm Mapping (Ánh xạ) là một kỹ thuật cực kỳ quan trọng khi làm Headless CMS. Nó đóng vai trò như một “bộ lọc” để ngăn chặn việc rò rỉ các thông tin nhạy cảm từ WordPress (như ID người dùng, email cá nhân, trạng thái bài viết…) xuống trình duyệt.
Dưới đây là cách thực hiện chuyên nghiệp trong Astro.js.
Tại sao phải Mapping?
Mặc định, WordPress REST API trả về một lượng dữ liệu khổng lồ cho mỗi bài viết.
- Dữ liệu thô từ WP: Có thể chứa hàng chục trường (fields) như
_links,ping_status,author_id, v.v. - Dữ liệu sau khi Map: Chỉ chứa đúng những gì UI cần (ví dụ:
title,slug,content,image).
Cách xây dựng hàm Mapping dữ liệu
Giả sử bạn cần lấy danh sách bài viết từ WordPress.
Bước 1: Định nghĩa hàm Filter
Tạo một file tiện ích, ví dụ: src/utils/mapData.js.
export function mapPostData(wpPost) {
// Chúng ta chỉ trích xuất các trường cần thiết
return {
id: wpPost.id,
title: wpPost.title.rendered,
slug: wpPost.slug,
content: wpPost.content.rendered,
excerpt: wpPost.excerpt.rendered,
date: new Date(wpPost.date).toLocaleDateString('vi-VN'),
// Xử lý ảnh đại diện nếu có
featuredImage: wpPost._embedded?.['wp:featuredmedia']?.[0]?.source_url || '/placeholder.jpg',
// Loại bỏ hoàn toàn các trường nhạy cảm như author_id, comment_status...
};
}
Bước 2: Sử dụng trong Astro (Server-side)
Trong file .astro của bạn, hãy thực hiện fetch và map dữ liệu ngay tại phần script (giữa hai dấu ---).
---
// src/pages/blog/index.astro
import { mapPostData } from '../../utils/mapData';
const response = await fetch('https://your-wp-site.com/wp-json/wp/v2/posts?_embed');
const rawPosts = await response.json();
// Áp dụng bộ lọc ngay tại Server
const posts = rawPosts.map(mapPostData);
---
<section>
{posts.map(post => (
<article>
<h2>{post.title}</h2>
<p>{post.date}</p>
</article>
))}
</section>
Luồng hoạt động của dữ liệu (Data Flow)
Để dễ hình dung, hãy xem sơ đồ dưới đây. Dữ liệu nhạy cảm bị chặn lại ở mức Server trước khi đến tay người dùng.
Mẹo nâng cao: Sử dụng TypeScript
Nếu bạn dùng TypeScript, việc mapping sẽ an toàn hơn nữa nhờ vào việc định nghĩa Interface. Điều này giúp bạn kiểm soát chặt chẽ kiểu dữ liệu và tránh lỗi undefined.
TypeScript
interface CleanPost {
title: string;
slug: string;
thumbnail: string;
}
export const cleanPost = (data: any): CleanPost => ({
title: data.title.rendered,
slug: data.slug,
thumbnail: data.featured_media_url
});
Lưu ý quan trọng
- Không bao giờ truyền trực tiếp biến
rawPostsvào một component có directiveclient:load. Nếu bạn làm vậy, Astro sẽ chuyển toàn bộ đống JSON thô đó vào mã nguồn HTML. - Sử dụng
_fieldscủa WordPress: Bạn có thể yêu cầu WP chỉ trả về một số trường nhất định ngay từ URL để giảm tải cho server:?_fields=author,id,excerpt,title,link
Hướng dẫn cách cấu hình Cloudflare hoặc Nginx để chặn truy cập trực tiếp vào các endpoint nhạy cảm của WordPress
Việc bảo mật ở tầng Server (Nginx) hoặc tầng Edge (Cloudflare) là lớp phòng thủ cuối cùng để bảo vệ WordPress khỏi các cuộc tấn công dò quét API và ngăn chặn việc lộ thông tin hệ thống.
Dưới đây là cách cấu hình cho cả hai nền tảng:
Cấu hình Nginx (Dành cho VPS/Server riêng)
Nếu bạn chạy WordPress trên Nginx, hãy mở file cấu hình site (thường ở /etc/nginx/sites-available/) và thêm các quy tắc sau:
Chặn truy cập danh sách User (Ngăn dò tìm Username)
Mặc định, WordPress trả về danh sách user tại /wp-json/wp/v2/users, đây là lỗ hổng lớn để hacker tấn công brute-force.
Nginx
location ~ ^/wp-json/wp/v2/users {
deny all;
return 403;
}
Chỉ cho phép Astro Front-end truy cập API (CORS)
Nếu Astro và WordPress nằm ở 2 domain khác nhau, hãy cấu hình chỉ cho phép domain của bạn:
Nginx
location /wp-json/ {
add_header 'Access-Control-Allow-Origin' 'https://your-astro-site.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
# Nếu là yêu cầu OPTIONS (preflight), trả về 204 ngay lập tức
if ($request_method = 'OPTIONS') {
return 204;
}
}
Cấu hình Cloudflare (Dễ nhất và hiệu quả cao)
Cloudflare cung cấp WAF (Web Application Firewall) rất mạnh mẽ mà không cần động vào code.
Cách tạo WAF Rule:
- Vào Dashboard Cloudflare -> Security -> WAF -> Create rule.
- Rule Name: “Protect WP-JSON and Login”.
- Expression Preview: Sử dụng logic sau:
(http.request.uri.path contains "/wp-json/wp/v2/users")-> Action:Block.(http.request.uri.path contains "/wp-admin" and ip.src ne 1.2.3.4)(Trong đó1.2.3.4là IP nhà bạn) -> Action:Block.
Chặn truy cập trực tiếp vào XML-RPC
XML-RPC thường bị dùng để tấn công DDoS. Nếu không dùng ứng dụng di động WordPress, hãy chặn nó:
- Field:
URI Path| Operator:equals| Value:/xmlrpc.php-> Action:Block.
Ẩn Backend hoàn toàn bằng Cloudflare Workers (Nâng cao)
Đây là cách “tối thượng”: Bạn có thể dùng Cloudflare Workers để tạo một Proxy.
- Front-end Astro sẽ gọi tới
https://api.yourdomain.com/... - Worker sẽ nhận request, thêm Secret Token vào Header, rồi mới forward tới WordPress thật.
- Kết quả: Hacker không hề biết URL thật của WordPress Backend nằm ở đâu.
Checklist bảo mật nhanh cho WordPress Backend
| Việc cần làm | Tại sao? |
| Đổi prefix Table | Thay wp_ bằng xyz_ để tránh SQL Injection mặc định. |
| Cài Plugin Application Passwords | Để Astro dùng mật khẩu riêng, không dùng mật khẩu Admin chính. |
| Tắt WP-JSON cho khách | Dùng code snippet để yêu cầu login mới xem được API. |
Lưu ý: Sau khi cấu hình Nginx, đừng quên chạy lệnh sudo nginx -t để kiểm tra lỗi và sudo systemctl reload nginx để áp dụng.
Code Snippet dán vào file functions.php của WordPress để tự động chặn các API nhạy cảm
Đây là các đoạn mã (Snippets) quan trọng nhất để bạn dán vào file functions.php của theme WordPress đang dùng. Những đoạn mã này giúp “thắt chặt” các lỗ hổng mà mặc định WordPress mở sẵn.
Chặn truy cập API Danh sách User
Hacker thường quét /wp-json/wp/v2/users để lấy danh sách username, từ đó tiến hành tấn công dò mật khẩu (Brute-force).
PHP
add_filter('rest_endpoints', function ($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});
Bắt buộc phải có Token mới được xem API
Đoạn code này sẽ chặn hoàn toàn khách vãng lai xem bất kỳ dữ liệu gì qua API. Chỉ những yêu cầu có thông tin đăng nhập (như Application Passwords từ Astro gửi lên) mới được phản hồi.
PHP
add_filter('rest_authentication_errors', function ($result) {
// Nếu đã có lỗi trước đó, bỏ qua
if (!empty($result)) {
return $result;
}
// Nếu người dùng chưa đăng nhập, trả về lỗi 401
if (!is_user_logged_in()) {
return new WP_Error('rest_not_logged_in', 'Bạn không có quyền truy cập dữ liệu này.', array('status' => 401));
}
return $result;
});
Loại bỏ các liên kết nhạy cảm trong Header HTML
Mặc định WordPress chèn rất nhiều thông tin “thừa” vào thẻ <head>, giúp kẻ xấu biết bạn đang dùng bản WordPress nào.
PHP
// Xóa link đến WP-JSON trong Header (nhưng không tắt API)
remove_action('wp_head', 'rest_output_link_wp_head', 10);
// Xóa thông tin phiên bản WordPress
remove_action('wp_head', 'wp_generator');
// Xóa link XML-RPC
remove_action('wp_head', 'rsd_link');
Vô hiệu hóa XML-RPC (Chống tấn công Brute-force/DDoS)
Hầu như các dự án Astro.js không cần dùng đến tính năng này.
PHP
add_filter('xmlrpc_enabled', '__return_false');
Tự động Mapping dữ liệu tại Backend (Tối ưu nhất)
Nếu bạn muốn “lọc” dữ liệu ngay khi nó rời khỏi WordPress (để Astro luôn nhận được dữ liệu sạch), bạn có thể dùng filter cho từng loại Post Type.
PHP
add_filter('rest_prepare_post', 'clean_post_api_response', 10, 3);
function clean_post_api_response($data, $post, $context) {
// Chỉ lọc khi ở chế độ xem 'view' (không ảnh hưởng đến admin chỉnh sửa)
if ($context !== 'view') return $data;
$response = [
'id' => $post->ID,
'title' => get_the_title($post->ID),
'slug' => $post->post_name,
'content' => apply_filters('the_content', $post->post_content),
// Bạn có thể thêm các trường ACF ở đây
];
return $response;
}
Lưu ý quan trọng khi thao tác:
- Luôn backup: Trước khi sửa
functions.php, hãy copy nội dung cũ ra một file text. - Sử dụng Child Theme: Nếu bạn dùng theme có sẵn, hãy viết code vào
functions.phpcủa Child Theme để không bị mất khi cập nhật theme. - Kiểm tra Astro: Sau khi dán code số 2, hãy đảm bảo Astro của bạn đã cấu hình gửi Authorization Header chính xác, nếu không Front-end sẽ báo lỗi 401.
Tạo Application Password trong WordPress để dùng làm mã kết nối bảo mật cho Astro
Việc sử dụng Application Password (Mật khẩu ứng dụng) là cách an toàn nhất để Astro.js “nói chuyện” với WordPress. Bạn không nên dùng mật khẩu Admin chính của mình, vì nếu lộ, hacker sẽ chiếm toàn quyền website.
Mật khẩu ứng dụng cho phép bạn thu hồi quyền truy cập bất cứ lúc nào mà không cần đổi mật khẩu chính.
Cách tạo Application Password
- Đăng nhập vào trang Quản trị WordPress (Dashboard).
- Đi tới mục Users (Thành viên) -> Profile (Hồ sơ của bạn).
- Cuộn xuống dưới cùng tìm phần Application Passwords.
- Tại ô New Application Password Name, hãy đặt một tên để dễ nhớ (ví dụ:
Astro Front-end). - Nhấn nút Add New Application Password.
- Lưu ý: Một mật khẩu ngẫu nhiên gồm 24 ký tự sẽ hiện ra. Hãy copy và lưu lại ngay, vì nó sẽ không hiển thị lại lần thứ hai.
Cách dùng mật khẩu này trong Astro.js (.env)
Đừng dán trực tiếp mật khẩu vào code. Hãy sử dụng file .env ở thư mục gốc của dự án Astro:
Đoạn mã
# .env (File này không được đưa lên GitHub)
WP_API_USER="admin"
WP_APPLICATION_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx"
WP_API_URL="https://your-wordpress-site.com/wp-json/wp/v2"
Cách gọi API bảo mật từ Astro
Khi bạn đã bật chặn API đối với khách (bằng code functions.php ở bước trước), bạn phải gửi kèm mã xác thực theo chuẩn Basic Auth trong mỗi lần fetch dữ liệu.
Đây là cách viết hàm fetch trong Astro (Server-side):
JavaScript
// src/lib/api.js
const username = import.meta.env.WP_API_USER;
const password = import.meta.env.WP_APPLICATION_PASSWORD;
// Mã hóa Base64 cho Header Authorization
const authHeader = Buffer.from(`${username}:${password}`).toString('base64');
export async function getPosts() {
const res = await fetch(`${import.meta.env.WP_API_URL}/posts?_embed`, {
headers: {
'Authorization': `Basic ${authHeader}`
}
});
if (!res.ok) {
throw new Error('Failed to fetch data from WordPress');
}
return res.json();
}
Tại sao cách này lại bảo mật?
- Mật khẩu nằm ở Server: Biến
WP_APPLICATION_PASSWORDkhông có tiền tốPUBLIC_, nên nó chỉ tồn tại trên Server của Astro. Trình duyệt của người dùng (Client) hoàn toàn không thấy chuỗi này. - Dễ dàng thu hồi: Nếu bạn nghi ngờ rò rỉ, bạn chỉ cần vào Profile trong WordPress và xóa mật khẩu đó đi.
- Quyền hạn giới hạn: Mật khẩu ứng dụng chỉ có quyền truy cập qua API, không thể dùng để đăng nhập vào giao diện Dashboard của WordPress.
Một mẹo nhỏ cho bạn:
Nếu bạn thấy việc quản lý thủ công các API request này hơi phức tạp, bạn có thể cài thêm Plugin WPGraphQL. Khi đó, bạn sẽ dùng Astro + Apollo Client hoặc urql để lấy dữ liệu. Cách này bảo mật hơn và dữ liệu trả về cực kỳ gọn nhẹ (không cần hàm mapping thủ công quá nhiều).
Hướng dẫn cách cài đặt và kết nối qua WPGraphQL – đang là “chuẩn” cho Headless WordPress hiện nay
Sử dụng WPGraphQL là một bước nâng cấp lớn cho dự án Astro của bạn. Thay vì nhận về một đống dữ liệu thừa từ REST API, bạn chỉ cần “đặt hàng” đúng những gì mình cần.
Dưới đây là các bước để chuyển đổi sang WPGraphQL.
Cấu hình trên WordPress (Backend)
- Cài đặt Plugin: Vào WordPress Dashboard -> Plugins -> Add New -> Tìm và cài đặt WPGraphQL.
- Mở GraphiQL IDE: Sau khi kích hoạt, bạn sẽ thấy mục GraphQL trên thanh menu. Hãy mở GraphiQL IDE. Đây là nơi bạn dùng để viết thử các câu truy vấn (Query) trước khi đưa vào code Astro.
- Cấu hình Endpoint: Mặc định, URL của bạn sẽ là
https://ten-mien-cua-ban.com/graphql.
Cấu hình trên Astro (Front-end)
Bạn không cần cài đặt thư viện nặng nề như Apollo Client nếu chỉ cần fetch dữ liệu đơn giản. Một hàm fetch cơ bản là đủ.
Bước 1: Cấu hình biến môi trường
Thêm vào file .env:
Đoạn mã
WORDPRESS_GRAPHQL_URL="https://your-site.com/graphql"
Bước 2: Viết hàm lấy dữ liệu
Tạo file src/lib/api.js:
JavaScript
export async function wpFullstackQuery(query, variables = {}) {
const res = await fetch(import.meta.env.WORDPRESS_GRAPHQL_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Nếu bạn đã chặn truy cập public, hãy thêm Authorization ở đây
// 'Authorization': `Basic ${Buffer.from('user:pass').toString('base64')}`
},
body: JSON.stringify({
query,
variables,
}),
});
const { data } = await res.json();
return data;
}
Cách lấy dữ liệu “Sạch” trong Astro
Thay vì nhận hàng chục trường dữ liệu và phải dùng hàm Mapping như trước, bạn chỉ liệt kê những trường cần lấy ngay trong câu Query.
Ví dụ lấy danh sách bài viết:
Đoạn mã
---
// src/pages/index.astro
import { wpFullstackQuery } from '../lib/api';
const data = await wpFullstackQuery(`
query GetPosts {
posts {
nodes {
title
slug
date
featuredImage {
node {
sourceUrl
}
}
}
}
}
`);
const posts = data.posts.nodes;
---
<ul>
{posts.map(post => (
<li>
<a href={post.slug}>{post.title}</a>
<span>{new Date(post.date).toLocaleDateString()}</span>
</li>
))}
</ul>
Tại sao WPGraphQL bảo mật và tốt hơn?
- Không rò rỉ dữ liệu ẩn: Bạn chỉ lấy
titlevàslug, thì WP cũng chỉ trả về đúng 2 trường đó. Không bao giờ có chuyện “lỡ tay” đưa mật khẩu hay email user xuống front-end. - Hiệu suất cực cao: Giảm đáng kể dung lượng JSON truyền tải qua mạng.
- Hỗ trợ ACF tuyệt vời: Nếu bạn dùng plugin Advanced Custom Fields (ACF), hãy cài thêm WPGraphQL for ACF để lấy dữ liệu custom cực kỳ dễ dàng.
Lưu ý về bảo mật cuối cùng
Khi dùng WPGraphQL, bạn nên cài thêm plugin WPGraphQL Lock hoặc cấu hình trong cài đặt WPGraphQL để:
- Tắt truy cập công khai: Chỉ cho phép các request có Token (từ Astro) mới được truy cập.
- Giới hạn độ sâu của Query: Ngăn chặn hacker gửi những câu query quá phức tạp để làm treo server (DDoS).
Ví dụ về Query GraphQL để lấy dữ liệu từ một Custom Post Type hoặc các trường ACF
Để lấy dữ liệu từ Custom Post Type (CPT) hoặc ACF qua GraphQL, bạn cần lưu ý một quy tắc quan trọng: Bạn phải “bật” tính năng GraphQL cho chúng trong WordPress.
Dưới đây là cách làm từ cấu hình đến viết câu Query.
Bật GraphQL cho Custom Post Type & ACF
Đối với Custom Post Type (CPT UI)
Nếu bạn dùng plugin CPT UI để tạo nội dung tùy chỉnh:
- Vào mục Edit Post Type.
- Tìm phần Settings.
- Chỉnh Show in GraphQL thành
True. - Đặt GraphQL Single Name (ví dụ:
sanPham) và GraphQL Plural Name (ví dụ:sanPhams).
Đối với ACF (Advanced Custom Fields)
- Bạn cần cài thêm plugin bổ trợ: WPGraphQL for ACF.
- Khi tạo Group Fields, ở phần Settings của Group đó, hãy bật Show in GraphQL và đặt tên cho nó (ví dụ:
thongTinSanPham).
Viết câu Query “Xịn” trong Astro
Giả sử bạn có một CPT là Sản phẩm và các trường ACF như Giá, Xuất xứ. Câu truy vấn trong Astro sẽ như thế này:
JavaScript
// Ví dụ Query trong file .astro
const data = await wpFullstackQuery(`
query GetSanPhams {
sanPhams { # Tên Plural Name bạn đã đặt
nodes {
id
title
slug
# Đây là phần lấy dữ liệu từ ACF
thongTinSanPham {
giaBan
xuatXu
mauSac
}
# Lấy ảnh đại diện
featuredImage {
node {
sourceUrl
altText
}
}
}
}
}
`);
const allProducts = data.sanPhams.nodes;
Cách hiển thị ra Front-end Astro
Dữ liệu lúc này đã rất “sạch” và gọn gàng, bạn chỉ cần render ra thôi:
Đoạn mã
<div class="product-grid">
{allProducts.map(product => (
<div class="card">
<img src={product.featuredImage?.node?.sourceUrl} alt={product.featuredImage?.node?.altText} />
<h3>{product.title}</h3>
<p>Giá: {product.thongTinSanPham.giaBan} VNĐ</p>
<p>Nguồn gốc: {product.thongTinSanPham.xuatXu}</p>
</div>
))}
</div>
Mẹo bảo mật bổ sung cho GraphQL
Khi bạn đã đưa mọi thứ lên GraphQL, hãy thực hiện thêm một bước bảo mật cuối cùng để tránh bị “quét” dữ liệu hàng loạt:
- Chặn Introspection (Tùy chọn): Trong cài đặt WPGraphQL, bạn có thể tắt tính năng “Introspection” trên môi trường Production. Việc này khiến hacker không thể dùng các công cụ như Postman để tự động dò xem bạn có những trường dữ liệu nào.
- Sử dụng Persisted Queries: Đây là kỹ thuật nâng cao, thay vì gửi một câu Query dài loằng ngoằng từ Astro, bạn chỉ gửi một cái “ID” của câu Query đó. WP sẽ chỉ thực thi câu Query khớp với ID đó.
Hướng dẫn cách cài đặt WPGraphQL for ACF
Để lấy được dữ liệu ACF qua GraphQL, bạn cần thực hiện theo 3 bước chuẩn chỉ dưới đây. Nếu thiếu một bước, các trường dữ liệu của bạn sẽ “tàng hình” đối với Astro.
Bước 1: Cài đặt Plugin bổ trợ
WPGraphQL mặc định không hiểu các trường tùy chỉnh của ACF. Bạn bắt buộc phải cài đặt thêm cầu nối:
- Tải và cài đặt plugin WPGraphQL for ACF (Đây là plugin miễn phí và chính thức từ cộng đồng).
- Sau khi kích hoạt, bạn sẽ thấy một tùy chọn mới xuất hiện trong các Group Fields của ACF.
Bước 2: Cấu hình ACF để “Lộ diện” trong GraphQL
Bạn phải cho phép từng nhóm trường (Field Group) xuất hiện trong API:
- Vào ACF -> Field Groups.
- Chọn nhóm trường bạn muốn lấy dữ liệu (ví dụ: “Thông tin sản phẩm”).
- Vào tab Settings (Cài đặt) của nhóm trường đó.
- Tìm mục Show in GraphQL: Chuyển sang Yes.
- GraphQL Field Name: Đặt tên (viết thường, không dấu, ví dụ:
productDetails). Đây sẽ là “key” để bạn gọi trong câu query.
Bước 3: Kiểm tra và Viết Query trong Astro
Bây giờ, hãy mở GraphQL IDE trong WordPress Dashboard để kiểm tra xem dữ liệu đã xuất hiện chưa.
Câu Query mẫu (Trong Astro):
Giả sử bạn có trường gia_ban và chat_lieu nằm trong nhóm productDetails.
JavaScript
// src/lib/api.js hoặc trực tiếp trong file .astro
const GET_PRODUCTS = `
query GetProducts {
posts { # Hoặc tên CPT của bạn
nodes {
title
productDetails { # Đây là GraphQL Field Name bạn đặt ở bước 2
giaBan # Tên trường ACF (viết theo kiểu camelCase)
chatLieu
}
}
}
}
`;
Bước 4: Lưu ý về định dạng tên trường (Rất quan trọng)
WPGraphQL thường tự động chuyển đổi tên trường từ snake_case (của ACF) sang camelCase.
- Nếu trong ACF bạn đặt là:
gia_ban - Thì trong câu query GraphQL bạn phải viết là:
giaBan
Mẹo bảo mật: Khi dùng WPGraphQL cho ACF, đừng bao giờ bật “Show in GraphQL” cho những nhóm trường chứa thông tin quản trị hoặc ghi chú nội bộ. Chỉ bật cho những gì bạn thực sự muốn hiển thị ra trang web.
Một điểm cần kiểm tra ngay:
Bạn đã tạo các trường ACF này cho Post (Bài viết) hay cho một Custom Post Type riêng? Nếu là Custom Post Type, hãy đảm bảo bạn cũng đã bật Show in GraphQL cho chính Post Type đó như tôi đã hướng dẫn ở phần trước nhé.
Còn vấn đề bảo mật nào hơn nữa không?
Khi bạn đã cấu hình đến mức này (Astro + WPGraphQL + Mapping + Security Snippets), bạn đã an toàn hơn 90% các website WordPress thông thường. Tuy nhiên, để đạt đến mức “nội bất xuất, ngoại bất nhập”, bạn cần xử lý thêm 3 vấn đề “ngầm” sau đây:
Bảo mật phía Client (Vấn đề Hydration)
Dù bạn dùng GraphQL để lọc dữ liệu, nhưng nếu bạn truyền nguyên một Object lớn vào một Component có tính năng tương tác (như React/Vue/Svelte với client:load), Astro sẽ thực hiện quá trình Hydration.
- Nguy cơ: Toàn bộ dữ liệu của Object đó sẽ bị biến thành một chuỗi JSON khổng lồ nằm trong mã nguồn HTML (view-source).
- Cách khắc phục: * Chỉ truyền các “primitive types” (string, number, boolean) vào component nếu có thể.
- Thay vì
<Card user={user} client:load />, hãy dùng<Card name={user.name} avatar={user.avatar} client:load />. Điều này đảm bảo những trường nhưuser.emailhayuser.internal_notekhông bao giờ xuất hiện ở trình duyệt.
- Thay vì
Giới hạn truy cập API bằng JWT (JSON Web Token)
Hiện tại bạn đang dùng Application Password (Basic Auth). Nó tốt, nhưng mỗi request đều gửi Username:Password (dạng mã hóa đơn giản).
- Nâng cấp: Sử dụng Plugin WPGraphQL JWT Authentication.
- Cơ chế: Astro sẽ đăng nhập một lần để lấy Token, sau đó dùng Token này để gọi dữ liệu. Token có thời gian hết hạn, giúp giảm thiểu rủi ro nếu bị chặn bắt gói tin.
Chống rò rỉ thông qua “Sơ đồ trang” (Introspection)
GraphQL có một tính năng rất hay nhưng cũng rất nguy hiểm là Introspection. Nó cho phép bất kỳ ai cũng có thể “hỏi” server: “Bạn có những hàm nào? Có những trường dữ liệu nào?”.
- Nguy cơ: Hacker dùng công cụ như
apollo-introspection-scannerđể tìm ra các trường dữ liệu ẩn hoặc các plugin bạn đang cài. - Cách xử lý: * Trong cài đặt WPGraphQL, hãy tắt Enable Public Introspection.
- Chỉ bật nó ở môi trường Local/Staging khi bạn cần viết code.
Bảo mật tầng Header (Security Headers)
Vì bạn dùng Astro, bạn có quyền kiểm soát hoàn toàn các Header trả về cho người dùng. Hãy thiết lập các header sau (thông qua file cấu hình của Vercel/Netlify hoặc Cloudflare):
| Header | Tác dụng |
| Content-Security-Policy (CSP) | Chỉ cho phép tải script/style từ các nguồn tin cậy, chặn đứng XSS. |
| X-Frame-Options | Chống bị nhúng trang web của bạn vào Iframe của kẻ xấu (Clickjacking). |
| Permissions-Policy | Chặn trình duyệt sử dụng Camera, Micro, Geolocation nếu web không cần. |
Giám sát rò rỉ Secret bằng công cụ CI/CD
Đôi khi bạn vô tình dán API Key vào code và git push lên GitHub.
- Giải pháp: Cài đặt trufflehog hoặc gitleaks vào quy trình GitHub Actions. Nó sẽ tự động quét toàn bộ code của bạn mỗi khi bạn push. Nếu phát hiện có chuỗi giống Password hay Token, nó sẽ ngăn chặn việc deploy và cảnh báo bạn ngay lập tức.
Tổng kết Checklist cuối cùng:
- [ ] Đã kiểm tra View Source (
Ctrl + U) xem có dữ liệu thừa trong thẻ<script type="application/json">không? - [ ] Đã thử truy cập
/wp-json/wp/v2/usersxem có bị chặn (401/403) chưa? - [ ] Đã tắt tính năng Introspection của GraphQL trên bản chính thức chưa?
- [ ] File
.envđã được đưa vào.gitignorechưa?
File .env đã được đưa vào .gitignore chưa?
Đây là một trong những lỗi “chí mạng” nhất mà nhiều lập trình viên mới (và cả lâu năm) hay mắc phải. Để tôi giải thích đơn giản cho bạn:
File .env là gì?
Như chúng ta đã làm ở các bước trước, file .env chứa những “bí mật quân sự” của trang web:
- Mật khẩu ứng dụng (Application Password).
- Token bí mật để kết nối WordPress.
- Cấu hình Database.
- Các API Key trả phí.
File .gitignore là gì?
Khi bạn dùng Git để quản lý code (và đẩy lên GitHub/GitLab), Git sẽ mặc định “theo dõi” và tải lên tất cả các file có trong thư mục dự án. .gitignore là một file văn bản đặc biệt dùng để ra lệnh cho Git rằng: “Này, hãy lờ những file này đi, đừng bao giờ đẩy chúng lên mạng!”
Tại sao phải đưa .env vào .gitignore?
Nếu bạn không đưa .env vào .gitignore, khi bạn thực hiện lệnh git push, toàn bộ mật khẩu của bạn sẽ bị đẩy lên GitHub.
- Nếu kho code là Public: Cả thế giới sẽ thấy mật khẩu WordPress của bạn. Hacker chỉ cần 30 giây để quét và chiếm quyền điều khiển website.
- Nếu kho code là Private: Vẫn không an toàn tuyệt đối, vì bất kỳ ai có quyền xem code (nhân viên, đối tác) cũng sẽ thấy mật khẩu đó.
Cách kiểm tra và thực hiện
Bước 1: Kiểm tra xem file .gitignore đã có chưa
Ở thư mục gốc của dự án Astro, tìm file có tên là .gitignore (nếu không thấy, hãy tạo mới một file tên đúng như vậy).
Bước 2: Thêm .env vào danh sách chặn
Mở file .gitignore và thêm dòng này vào (thường nó sẽ nằm ở ngay đầu):
.env
.env.*
node_modules/
dist/
Bước 3: Kiểm tra trạng thái Git
Mở terminal và gõ: git status
- Nếu an toàn: Bạn sẽ KHÔNG thấy file
.envxuất hiện trong danh sách “Untracked files” hoặc “Changes to be committed”. - Nếu nguy hiểm: Nếu bạn thấy
.envnằm trong danh sách file sắp được tải lên, hãy dừng lại ngay!
Lỡ tay đẩy .env lên GitHub rồi thì sao?
Nếu bạn đã lỡ push và nhìn thấy file .env trên giao diện web của GitHub:
- Xóa file khỏi lịch sử Git: Dùng lệnh:
git rm --cached .env(Lệnh này xóa file trên Git nhưng vẫn giữ lại file ở máy bạn). - Commit và Push lại:
git commit -m "Remove sensitive config"git push - QUAN TRỌNG NHẤT: Đổi mật khẩu ngay lập tức! Một khi đã lên GitHub, mật khẩu đó coi như đã bị lộ. Hãy vào WordPress tạo một “Application Password” mới và xóa cái cũ đi.
Làm sao để deploy lên mạng nếu không có file .env?
Bạn có thể thắc mắc: “Nếu không đẩy .env lên GitHub, thì làm sao Hosting (Vercel, Netlify) biết mật khẩu để kết nối?”
- Trả lời: Bạn phải nhập các biến đó thủ công vào phần Environment Variables trong bảng điều khiển (Dashboard) của dịch vụ Hosting đó. Đây là cách làm chuẩn và an toàn nhất.
Hướng dẫn cách thiết lập Secret trên GitHub Actions hoặc Hosting để web vẫn chạy mà không cần đẩy file .env lên
Việc thiết lập Environment Variables (Biến môi trường) trực tiếp trên Hosting là bước cuối cùng để hoàn thiện quy trình bảo mật. Khi đó, code của bạn trên GitHub hoàn toàn “sạch”, không chứa mật khẩu, nhưng khi web chạy thực tế (Deploy) thì nó vẫn kết nối được với WordPress.
Dưới đây là hướng dẫn cho 2 nền tảng phổ biến nhất khi dùng Astro:
Nếu bạn dùng Vercel (Phổ biến nhất cho Astro)
- Truy cập vào Vercel Dashboard và chọn Project của bạn.
- Đi tới tab Settings (Cài đặt).
- Chọn mục Environment Variables ở cột bên trái.
- Tại đây, bạn nhập từng cặp Key và Value tương ứng như trong file
.envcủa bạn:- Key:
WP_APPLICATION_PASSWORD| Value:xxxx xxxx xxxx xxxx - Key:
WORDPRESS_GRAPHQL_URL| Value:https://your-site.com/graphql
- Key:
- Nhấn Save.
- Quan trọng: Sau khi lưu, bạn cần Redeploy (triển khai lại) để Vercel nạp các biến mới này vào hệ thống.
Nếu bạn dùng Netlify
- Vào Dashboard của Netlify -> Chọn trang web của bạn.
- Đi tới Site configuration -> Environment variables.
- Nhấn Add a variable -> Import from .env (Bạn có thể dán nội dung file .env vào cho nhanh) hoặc nhập từng cái một.
- Nhấn Create.
- Tương tự, hãy thực hiện một lượt Deploy mới để áp dụng.
Cách kiểm tra trong code Astro
Khi bạn đã nhập biến trên Hosting, trong code Astro, bạn vẫn gọi chúng thông qua import.meta.env:
JavaScript
// Dù ở máy cá nhân hay trên Hosting, cách gọi này đều giống nhau
const apiUrl = import.meta.env.WORDPRESS_GRAPHQL_URL;
Tại sao làm cách này lại an toàn?
- Không nằm trong Repo: File
.envchỉ nằm ở máy của bạn (Local). - Mã hóa đầu cuối: Các dịch vụ như Vercel/Netlify mã hóa các giá trị này trong hệ thống của họ.
- Tách biệt môi trường: Bạn có thể đặt mật khẩu cho bản chạy thử (Preview) khác với bản chạy thật (Production).
Mẹo cho GitHub Actions (Nếu bạn tự CI/CD)
Nếu bạn dùng GitHub Actions để tự build và đẩy lên server riêng (VPS), bạn không được để lộ mật khẩu trong file .yml.
- Vào Repo trên GitHub -> Settings -> Secrets and variables -> Actions.
- Nhấn New repository secret.
- Trong file
.yml, bạn gọi nó bằng cú pháp:${{ secrets.WP_APPLICATION_PASSWORD }}.
Tóm tắt quy trình chuẩn:
- Viết code và kiểm tra với file
.envở máy cá nhân. - Đảm bảo đã có
.envtrong file.gitignore. - Đẩy code lên GitHub (lúc này code không có mật khẩu).
- Lên Vercel/Netlify điền mật khẩu vào phần Environment Variables.
- Hệ thống tự động Build và chạy web với mật khẩu đó.
Kết luận
WordPress Headless kết hợp Astro.js mang lại hiệu năng và khả năng mở rộng tốt hơn so với WordPress truyền thống, nhưng nếu không chú ý bảo mật, nó cũng tạo ra nhiều bề mặt rò rỉ dữ liệu.
Để an toàn, cần tuân thủ nguyên tắc: không lộ secrets ra front-end (không dùng tiền tố PUBLIC_ cho biến nhạy cảm), map dữ liệu trước khi truyền vào component, tránh gọi API WordPress trực tiếp từ trình duyệt và dùng Astro như lớp trung gian.
Áp dụng Application Password hoặc JWT thay vì dùng mật khẩu Admin, tắt hoặc giới hạn các endpoint nhạy cảm (/wp-json/wp/v2/users, xmlrpc.php), và chặn introspection GraphQL trên production. Thiết lập CORS/Nginx/Cloudflare để chỉ cho phép domain đáng tin cậy, kích hoạt WAF, và sử dụng Cloudflare Worker nếu cần ẩn backend.
Luôn thêm .env vào .gitignore, lưu secrets trong Environment Variables của hosting/CI (Vercel/Netlify/GitHub Actions) và quét mã bằng gitleaks/trufflehog trước khi deploy.
Cuối cùng, bật các security headers (CSP, X-Frame-Options, Permissions-Policy) và kiểm tra bằng Network tab + view-source để đảm bảo không có dữ liệu thừa trong <script type=”application/json”>.
Thực hiện đầy đủ các bước này sẽ giảm rủi ro bảo mật đáng kể và giữ cho hệ thống Headless WordPress + Astro.js an toàn khi vận hành.









