WordPress Application Passwords (introduced in WordPress 5.6) provide a mechanism for external applications, scripts, and services to authenticate against the WordPress REST API and XML-RPC without exposing the user’s main login password. Each application password is a 24-character credential generated per-user in the WordPress admin under Users → Profile → Application Passwords, or programmatically via WP_Application_Passwords::create_new_application_password() — it is displayed to the user once in plaintext and then stored as a bcrypt hash in the _application_passwords user meta key, so a database compromise does not expose usable credentials. Authentication uses HTTP Basic Auth with the WordPress username as the user and the application password as the password (spaces in the 24-character credential are optional). Every application password entry records its name, UUID, creation timestamp, last-used timestamp, and last-used IP address, all visible in the user’s profile — enabling individual revocation of credentials for compromised integrations without affecting other connected apps or requiring a main password change. Application passwords are disabled by default on HTTP sites — wp_is_application_passwords_available() returns false unless the site uses HTTPS, preventing plaintext credential transmission; disable the feature entirely with add_filter( 'wp_is_application_passwords_available', '__return_false' ) for sites that do not expose the REST API publicly. Alternative authentication methods: nonce-based cookie auth (wp_create_nonce( 'wp_rest' ) + X-WP-Nonce header) is session-scoped and appropriate only for JavaScript running inside wp-admin; JWT plugins issue expiring tokens suitable for mobile apps; OAuth 2.0 provides the full authorization-code flow for third-party app authorization. Application passwords sit between nonces (single-session, browser-only) and OAuth (full authorization flow) — they are ideal for server-to-server integrations and scripts that need stable, long-lived credentials tied to a specific WordPress user. The TOTP 2FA post covered securing the interactive login; application passwords complement that by providing API credentials that are separate from and unaffected by the 2FA-protected interactive session.
Problem: A Node.js inventory sync script updates WooCommerce product stock via the REST API every 5 minutes using the shop manager’s main login password in a .env file. When the shop manager changes their password, the sync breaks silently — and the credential is shared across three servers with no rotation process.
Solution: Create a dedicated inventory-sync-bot WordPress user with the shop_manager role, generate an application password for REST API use only, and add a filter that restricts that credential to the WooCommerce products endpoints — so a leaked credential cannot be used for any other operation.
// ── Create dedicated user and provision an application password ────────────
function myplugin_provision_sync_credentials(): void {
$username = 'inventory-sync-bot';
$user_id = username_exists( $username );
if ( ! $user_id ) {
$user_id = wp_insert_user( [
'user_login' => $username,
'user_pass' => wp_generate_password( 32, true, true ),
'user_email' => 'sync-bot@' . wp_parse_url( home_url(), PHP_URL_HOST ),
'role' => 'shop_manager',
] );
if ( is_wp_error( $user_id ) ) return;
}
// $new_password is the plaintext credential — available only at creation time
[ $new_password, $new_item ] = WP_Application_Passwords::create_new_application_password(
$user_id,
[ 'name' => 'ERP Inventory Sync v1' ]
);
// Store $new_password in a secrets vault, not in error_log on production
error_log( 'App password UUID: ' . $new_item['uuid'] );
}
// ── Restrict the credential to WooCommerce products endpoints only ────────
add_filter( 'rest_authentication_errors', 'myplugin_restrict_sync_user_routes' );
function myplugin_restrict_sync_user_routes( $result ) {
if ( ! is_user_logged_in() ) return $result;
$user = wp_get_current_user();
if ( 'inventory-sync-bot' !== $user->user_login ) return $result;
$request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
$allowed_prefix = '/' . rest_get_url_prefix() . '/wc/v3/products';
if ( false === strpos( $request_uri, $allowed_prefix ) ) {
return new WP_Error(
'rest_forbidden_route',
'This credential is restricted to WooCommerce product endpoints.',
[ 'status' => 403 ]
);
}
return $result;
}
// Node.js: call the REST API using application password (HTTP Basic Auth)
import fetch from 'node-fetch';
const WP_URL = process.env.WP_URL;
const WP_USER = process.env.WP_USER; // inventory-sync-bot
const WP_APP_PW = process.env.WP_APP_PW; // abcd efgh ijkl mnop qrst uvwx
const credentials = Buffer.from(`${WP_USER}:${WP_APP_PW}`).toString('base64');
async function updateProductStock(productId, stockQuantity) {
const response = await fetch(`${WP_URL}/wp-json/wc/v3/products/${productId}`, {
method: 'PUT',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ stock_quantity: stockQuantity, manage_stock: true }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`WC API ${response.status}: ${error.message}`);
}
return response.json();
}
# WP-CLI: manage application passwords
# Create a new application password (prints plaintext once)
wp user application-password create inventory-sync-bot "ERP Sync v2" --porcelain
# List all application passwords for a user
wp user application-password list inventory-sync-bot --fields=uuid,name,last_used,last_ip --format=table
# Revoke a specific credential by UUID
wp user application-password delete inventory-sync-bot "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Emergency: revoke ALL application passwords for a user
wp user application-password list inventory-sync-bot --format=ids | xargs -I{} wp user application-password delete inventory-sync-bot {}
NOTE: Application passwords authenticate as the WordPress user and respect that user’s role capabilities exactly as if they were logged in to wp-admin. A shop_manager application password can do everything a shop manager can do — including reading all orders and creating coupons. The per-endpoint restriction filter adds an extra access-control layer but is not a substitute for assigning the correct minimal role to the dedicated user. Also note: application passwords cannot be used to log in to the wp-admin dashboard via the login form — they only work for REST API and XML-RPC authentication. Creating a dedicated service account whose only authentication mechanism is an application password effectively prevents interactive wp-admin login for that account, which is a desirable security property for automated service accounts.