Securing a headless WordPress setup, which pairs a robust CMS like WordPress with modern frontend frameworks such as Astro, Next.js, Nuxt, or SvelteKit, introduces unique security challenges. A common vulnerability is leaving the GraphQL endpoint exposed, which can be likened to publicly sharing the structural plans of your home.
An unprotected **/graphql**
endpoint allows malicious actors to:
- Discover your entire data structure: This includes custom post types, fields, and other schema details.
- Launch denial-of-service (DoS) attacks: By executing complex, resource-intensive queries that can overload and crash your server.
- Access sensitive content: Private or draft content may become accessible if permissions are misconfigured.
This guide outlines a comprehensive, multi-layered strategy to restrict access to your WPGraphQL endpoint, ensuring only your authorized frontend application can interact with it.
WPGraphQL Plugin’s Built-in Security
The WPGraphQL plugin offers an initial security measure by allowing you to restrict endpoint access to authenticated users.
To enable this:
- Navigate to GraphQL → Settings in your WordPress dashboard.
- Activate the Restrict Endpoint to Authenticated Users toggle.
- Save your changes.
Once enabled, any unauthenticated request to `https://yourdomain.com/graphql` will be blocked, establishing your first line of defense.
Leveraging WordPress Application Passwords
WordPress features Application Passwords, a native mechanism for generating unique credentials for external applications without exposing your primary login password.
How to create one:
- In the WordPress admin, go to Users → Profile.
- Locate the Application Passwords section.
- Provide a descriptive name (e.g.,
My Frontend App
) and click Add New Application Password. - Crucially, copy the generated password immediately as it will not be displayed again.
This password will be essential for authenticating API requests from your frontend.
Making Authenticated API Calls from Your Frontend
Let’s integrate the Application Password for secure communication with your WordPress backend.
1. Environment Variable Management
Never hardcode sensitive information. Instead, store credentials in a .env.local
file within your frontend project:
# .env.local
WP_GRAPHQL_URL="https://yourdomain.com/graphql"
WP_USER="your_wordpress_username"
WP_APP_PASSWORD="your-generated-app-password"
Important: Add
.env.local
to your.gitignore
to prevent credentials from being committed to version control.
2. Crafting a Reusable Fetch Helper
Implement a secure fetchAPI
function for consistent, authenticated requests:
// lib/graphql.js
/**
* Perform an authenticated fetch to the WordPress GraphQL API.
* @param {string} query - The GraphQL query string.
* @param {object} [variables={}] - Query variables.
* @returns {Promise<any>} - GraphQL response data.
*/
export async function fetchAPI(query, variables = {}) {
const endpoint = process.env.WP_GRAPHQL_URL;
const username = process.env.WP_USER;
const password = process.env.WP_APP_PASSWORD;
if (!endpoint) {
throw new Error("WP_GRAPHQL_URL is not defined in environment variables.");
}
const headers = { "Content-Type": "application/json" };
// Add Basic Auth if credentials exist
if (username && password) {
headers["Authorization"] = "Basic " + btoa(`${username}:${password}`);
} else {
console.warn(
"WordPress credentials not set. Falling back to unauthenticated request."
);
}
const res = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
console.error("GraphQL errors:", json.errors);
throw new Error("Failed to fetch from WordPress API");
}
return json.data;
}
You can now securely query WordPress:
const query = `
query GetPosts {
posts {
nodes {
title
slug
}
}
}
`;
fetchAPI(query).then((data) => console.log(data));
Advanced Security Measures (Defense in Depth)
While application-level authentication is effective, production environments benefit from additional safeguards.
1. Implementing a Secret Header
Require all requests to **/graphql**
to include a unique, private header.
Frontend fetch modification:
const headers = {
"Content-Type": "application/json",
"X-Secret-Request-Header": process.env.SECRET_HEADER_KEY,
};
.env.local
addition:
SECRET_HEADER_KEY="a-very-long-random-string"
Nginx configuration example to enforce this:
location = /graphql {
if ($http_x_secret_request_header != "a-very-long-random-string") {
return 403; # Forbidden
}
try_files $uri $uri/ /index.php?$args;
}
2. Firewall or Hosting-Level Rules
- IP Whitelisting: Configure your firewall or hosting provider to permit requests to
**/graphql**
only from the specific IP addresses of your frontend application or serverless functions. - Header Enforcement: Utilize firewall rules to mandate the presence of your secret header for all
**/graphql**
requests. - Deny All Others: Explicitly block all other incoming requests to the
**/graphql**
endpoint.
3. Disabling the WordPress REST API (If Unused)
If your headless setup doesn’t utilize the WordPress REST API for anonymous visitors, consider disabling it to prevent potential user enumeration and other attacks:
<?php
// functions.php
add_filter('rest_api_init', function () {
if (!is_user_logged_in()) {
wp_die(
'The REST API is disabled for public access.',
'rest_api_disabled',
array('status' => 401)
);
}
});
This prevents public access to endpoints like **/wp-json/wp/v2/users**
.
By integrating these multiple security layers, your **/graphql**
endpoint can be effectively shielded from unauthorized access, maintaining secure communication with your frontend application.