← Back to Home

WordPress REST API Security

wordpressrest-apisecuritycmsweb-hosting

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:

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:

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:

☁️ DigitalOcean Cloud ⚡ Vultr VPS ⭐ MiniMax Token Plan 🧩 Zhipu Coding Plan 🎁 Zhipu 20M Tokens Gift 🤖 QoderWork CN (Refer & Earn) ☁️ Aliyun AI Products 📚 WordPress Books 🔍 WordPress SEO Books 🌐 Web Hosting Books 🐳 Docker Books 🐧 Linux Books 🐍 Python Books 💰 Affiliate Marketing 💵 Passive Income Books 🖥️ Server Books ☁️ Cloud Computing Books 🚀 DevOps Books
← Back to Home