WordPress admin username exposure via wp-json REST API
WordPress REST API exposes admin usernames at /wp-json/wp/v2/users by default. Attackers use this for targeted brute-force. Here's how to detect and stop it.
WordPress's REST API, enabled by default since version 4.7, makes admin usernames publicly discoverable at /wp-json/wp/v2/users. Combined with the old ?author=1 redirect technique, username enumeration requires no credentials and no special tooling — just a curl command.
The /wp-json/wp/v2/users endpoint
WordPress exposes an authenticated user list through its REST API. The endpoint returns JSON objects for each user, including their name (display name) and slug (login name). For sites where the admin hasn't changed their username from admin or where the display name matches the login name, this is a direct credential.
curl -s https://yourdomain.com/wp-json/wp/v2/users | python3 -m json.toolA typical response looks like this:
[
{
"id": 1,
"name": "site-admin",
"slug": "site-admin",
"link": "https://yourdomain.com/author/site-admin/",
...
}
]The slug field is the WordPress username. This is what attackers use as the first half of a brute-force pair.
The ?author=1 redirect method
An older technique that still works on many sites: request https://yourdomain.com/?author=1 and follow the redirect:
curl -s -I https://yourdomain.com/?author=1 | grep -i locationWordPress redirects to the author archive URL, which typically contains the username in the path: /author/site-admin/. Increment the ID to enumerate all registered users. This method works even when the REST API user endpoint is disabled, because it relies on the author archive routing.
What attackers do with exposed usernames
Once an attacker knows the administrator username, brute-force attacks become far more efficient. Instead of testing username/password combinations across the full space, they can target one specific username with a large password dictionary against /wp-login.php or, worse, via XML-RPC's multicall method (see: the XML-RPC security article).
Exposed usernames also enable:
- Credential stuffing — checking breached password lists against the known username. If the site admin reused a password from a previous breach, it often succeeds.
- Social engineering — the exposed
namefield reveals the admin's display name, which can be used in targeted phishing. - User enumeration for multi-user sites — WooCommerce stores and membership sites may expose hundreds of customer usernames, enabling bulk credential attacks.
How to disable the users REST API endpoint
Via functions.php (child theme or custom plugin)
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;
});Add this to your child theme's functions.php or a site-specific plugin. Do not edit the parent theme's functions.php — it will be overwritten on theme updates.
Require authentication for user endpoint
An alternative that preserves the endpoint for authenticated requests (useful if you have a headless frontend that needs user data):
add_filter('rest_endpoints', function($endpoints) {
if (!is_user_logged_in()) {
if (isset($endpoints['/wp/v2/users'])) {
$endpoints['/wp/v2/users'][0]['permission_callback'] = function() {
return current_user_can('list_users');
};
}
}
return $endpoints;
});Blocking the ?author= redirect
At the web server level (nginx), redirect author archive requests to 404 or the homepage:
if ($args ~ "author=\d") {
return 301 /;
}Or handle in WordPress (slower — PHP still executes):
add_action('template_redirect', function() {
if (is_author()) {
wp_redirect(home_url(), 301);
exit;
}
});Username hardening beyond enumeration prevention
- Change the admin username — if user ID 1 has username
admin, create a new admin account with a non-obvious username, transfer content ownership, then delete theadminaccount. WordPress does not support renaming usernames natively. - Use distinct display names — set the display name to something different from the login name (Settings → Users → Your Profile → Display name publicly as). The REST API returns display name, not login name, but the
slugfield still exposes the login name. - Enable 2FA — username exposure matters far less if the login requires a second factor.
- Implement login rate limiting — at the web server level, not just plugin level.
Common mistakes
- Disabling REST API entirely — the WordPress REST API is used by Gutenberg editor, many plugins, and legitimate headless setups. Disabling it completely breaks things. Restrict the users endpoint specifically.
- Blocking /wp-json but forgetting the ?author= redirect — two separate vectors, require two separate fixes.
- Using display name as login name — WordPress's default for new accounts uses the username as the display name. Change display names to first name, full name, or a non-login identifier.
Orb44 detects username exposure via both the REST API endpoint and the author redirect method, and flags it as part of WordPress-specific security scanning.
FAQ
Does disabling the users endpoint break anything?
The Gutenberg editor and standard WordPress plugins don't require the public users endpoint. Some headless WordPress setups (Next.js frontend pulling from the REST API) may use it — review your architecture before disabling. The "require authentication" approach above is safer for headless deployments.
Is username exposure a critical vulnerability?
On its own, it's a low-to-medium severity information disclosure. Combined with weak or reused passwords and no 2FA or rate limiting, it becomes a direct path to site compromise. Fix the exposure and the underlying authentication weaknesses together.