WordPress REST API Security
WordPress REST API has been the backbone of WordPress modernization since version 4.7 (2016). Yet the default configuration leaves multiple real attack vectors open: username enumeration, missing permission callbacks, and sensitive field exposure. In 2026, these vulnerabilities remain among the most exploited WordPress attack surfaces. This article covers 5 real-world security pitfalls and the minimum viable fixes — all verified.
If you have already hardened your WordPress site with Cloudflare Tunnel and MCP Adapter security (covered in previous articles), the REST API is the next critical layer to lock down.
TL;DR
🔒 Priority fixes (in order):
1. Block user enumeration: add REST_API_ENUM_USERS filter in wp-config.php
2. Add permission_callback to every register_rest_route call
3. Filter response fields: use rest_prepare_user to strip sensitive data
4. Enforce nonce verification for authenticated requests
5. Rate-limit at nginx layer + block XML-RPC
What WordPress REST API exposes by default
WordPress REST API is enabled by default and allows anonymous read access to most endpoints. Without configuration, attackers can retrieve:
- /wp-json/wp/v2/users -> complete user list (when rest_user_query is not restricted)
- /wp-json/wp/v2/users/{id} -> individual user ID, slug, and display name
- /wp-json/ -> full manifest of all registered endpoints on the site
Real case: In late 2025, a massive WordPress credential stuffing campaign exploited /wp-json/wp/v2/users to enumerate admin usernames, then ran brute-force attacks against weak passwords. This is not theoretical -- it is the actual attack chain.
Fix 1: Block user enumeration (most critical)
WordPress 4.7+ allows anonymous access to /wp-json/wp/v2/users by default. This is the root cause of username enumeration attacks.
Minimum viable fix -- add to wp-config.php or theme functions.php:
[code]
// Block user enumeration via REST API
add_filter( 'rest_user_query', function( $args, $request ) {
if ( ! current_user_can( 'list_users' ) ) {
$args['who'] = 'authors';
$args['has_published_posts'] = true;
}
return $args;
}, 10, 2 );
[/code]
Or use a plugin-level approach -- WP Hardening plugin's "Secure REST API" option -- or in functions.php:
[code]
// Method 2: completely disable user endpoint
add_filter( 'rest_endpoints', function( $endpoints ) {
if ( isset( $endpoints['/wp/v2/users'] ) ) {
$endpoints['/wp/v2/users'] = array_filter( $endpoints['/wp/v2/users'], function( $route ) {
return false; // Remove all user routes
});
}
return $endpoints;
});
[/code]
Verification: curl https://yoursite.com/wp-json/wp/v2/users | jq '.code' should return rest_forbidden or an empty array -- not a list of usernames.
Fix 2: Add permission_callback to every register_rest_route
This is the most common REST API security misconfiguration. When permission_callback is omitted from register_rest_route, it defaults to null -- meaning public access.
Dangerous (incorrect):
[code]
add_action( 'rest_api_init', function() {
register_rest_route( 'myplugin/v1', '/sensitive-data', array(
'methods' => 'GET',
'callback' => function( $request ) {
return get_option( 'my_secret_api_key' ); // Accessible to anyone!
},
// Missing permission_callback!
) );
} );
[/code]
Correct:
[code]
add_action( 'rest_api_init', function() {
register_rest_route( 'myplugin/v1', '/sensitive-data', array(
'methods' => 'GET',
'callback' => function( $request ) {
return get_option( 'my_secret_api_key' );
},
'permission_callback' => function( $request ) {
return current_user_can( 'manage_options' ); // Admin only
},
) );
} );
[/code]
WordPress official documentation requires permission_callback on all register_rest_route calls. This is a mandatory code review check for any theme/plugin registered after 2026.
Fix 3: Filter sensitive response fields
WordPress default user endpoints expose far more fields than business needs require. Use rest_prepare_user or register_rest_field to control exactly what gets returned.
[code]
// Strip sensitive user fields from REST responses
add_filter( 'rest_prepare_user', function( $response, $user, $request ) {
unset( $response->data['capabilities'] );
unset( $response->data['user_email'] ); // Do not expose email publicly
unset( $response->data['user_pass'] ); // Password hash: never
unset( $response->data['email'] );
return $response;
}, 10, 3 );
// Also filter custom endpoints
add_filter( 'rest_prepare_post', function( $response, $post, $request ) {
if ( $request->get_method() !== 'POST' && $request->get_method() !== 'PUT' ) {
unset( $response->data['modified_gmt'] );
unset( $response->data['guid'] );
}
return $response;
}, 10, 3 );
[/code]
Real pitfall: An e-commerce site using WooCommerce REST API for order sync was exposing full customer addresses (street + phone) through /wc/v3/orders without authentication. Attackers enumerated order IDs and harvested thousands of customer records. The fix: apply woocommerce_rest_prepare_order_object filter to strip billing_phone and billing_address fields.
Fix 4: Secure Application Passwords configuration
WordPress 5.6+ ships with Application Passwords -- a built-in way for third-party apps to authenticate with REST API using per-app passwords. Misconfiguration opens an attack vector.
Correct usage workflow:
[code]
# 1. Admin login -> Users -> Profile -> Application Passwords
# 2. Generate a named password (e.g., "woocommerce-ios-app")
# 3. Password shown once -- store it securely (Bitwarden, 1Password, etc.)
[/code]
Secure API call example:
[code]
$args = array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( 'username:application_password' ),
'Content-Type' => 'application/json',
),
);
$response = wp_remote_post( 'https://yoursite.com/wp-json/wp/v2/posts', $args );
[/code]
Security notes:
- Application Passwords cannot be used to log into wp-admin -- only REST API access
- Each third-party integration should have its own password; revoke immediately on any suspected compromise
- Use only over HTTPS -- plaintext HTTP exposes the password to MITM attacks
- WordPress 6.9+ supports Application Passwords combined with 2FA for stronger protection
Fix 5: Rate limiting and DDoS protection at nginx layer
WordPress REST API has no built-in rate limiting. Attackers can hammer /wp-json/ endpoints for brute force enumeration or DoS.
Minimum viable nginx rate limiting configuration:
[code]
limit_req_zone $binary_remote_addr zone=wp_api:10m rate=30r/m;
location /wp-json/ {
limit_req zone=wp_api burst=5 nodelay;
error_page 503 = @rate_limit_exceeded;
}
location @rate_limit_exceeded {
return 429 '{"error": "Too Many Requests", "retry_after": 60}';
}
[/code]
WordPress plugin alternative: WP REST API Controller plugin provides visual endpoint management and per-route rate limiting without touching nginx.
Also block XML-RPC -- it is a more commonly exploited entry point than REST API for brute force attacks:
[code]
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
[/code]
Verification checklist: 5-minute self-audit
Run these curl commands against your WordPress site:
[code]
# Check user enumeration is blocked
curl -s https://yoursite.com/wp-json/wp/v2/users | jq '.code'
# Expected: rest_forbidden or empty array -- NOT a username list
# Check authenticated endpoints are protected
curl -s https://yoursite.com/wp-json/wp/v2/settings | jq '.code'
# Expected: rest_forbidden when not authenticated
# Check XML-RPC is blocked
curl -I https://yoursite.com/xmlrpc.php
# Expected: 403 Forbidden
[/code]
Common FAQ
Q: Will disabling REST API break Gutenberg editor?
A: No. Gutenberg uses /wp-json/wp/v2/ internal endpoints -- the same API as public endpoints. Blocking user enumeration (Fix 1) does not affect the editor. Completely disabling REST API will break Gutenberg, FSE themes, and most modern plugins. Do not do it unless you have a specific reason.
Q: My site uses Cloudflare -- do I still need nginx rate limiting?
A: Yes. Cloudflare free plan has only 3 firewall rules. API rate limiting needs to be granular at the application layer (nginx/PHP) -- Cloudflare handles DDoS globally, but application-level rate limits prevent API-specific abuse that Cloudflare might not catch.
Q: JWT Authentication plugins vs Application Passwords -- which is more secure?
A: Application Passwords (built into WordPress 5.6+) is the better choice: no third-party plugin dependency, integrates with WordPress user management, and revocation is instant. JWT plugins require manual key rotation, and if keys leak on a site without HTTPS, they are fully compromised. Application Passwords + 2FA is the 2026 recommended approach.
Q: Does WooCommerce REST API need special hardening?
A: WooCommerce REST API requires OAuth 1.0 or Application Passwords by default -- safer than core WordPress endpoints. But order and customer endpoints (/wc/v3/orders, /wc/v3/customers) can still leak data if permission_callback is misconfigured. Use WooCommerce official REST API auth guide and apply woocommerce_rest_prepare_order_object filter to strip billing_phone and billing_address fields.
Summary: WordPress REST API security is not a binary on/off decision -- it is about fine-grained configuration. The two highest-impact fixes are blocking user enumeration (prevents username disclosure) and adding permission_callback to every custom endpoint (prevents unauthorized access). Both cost almost nothing to implement and prevent the most common real-world attack vectors.
If you are using MCP Adapter to connect WordPress with AI tools, the /wp-json/mcp/ endpoint hardening is covered in WordPress 7.0 MCP Adapter Security Hardening in Production.
👉 Join MiniMax Token Plan: AI coding acceleration for businesses
👉 Join Zhipu Coding Plan: GLM-4.6/GLM-5 coding packages, China-stable, pay-per-token unlimited
👉 Join Aliyun AI: Top AI products with exclusive coupons for business innovation
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
🔗 Recommended Tools
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: