WordPress handles login failed in two ways:
-
If it is a bad credential, and both username and password have a value, then this action can be captured by wp_login_failed
-
If both, or one, of the options are empty, then WordPress generates the error object as the first parameter in the authenticate filter; it does not open and wp_login_failed action captures this cause/event For what we have done here,
see comments in code:
add_filter( 'authenticate', function( $user, $username, $password ) {
// forcefully capture login failed to forcefully open wp_login_failed action,
// so that this event can be captured
if ( empty( $username ) || empty( $password ) ) {
do_action( 'wp_login_failed', $user );
}
return $user;
} );
// to handle even you can handle the error like
add_action( 'wp_login_failed', function( $username ) {
if ( is_wp_error( $username ) ) {
// you return 401 with wp function, this action takes place before header sent.
$wp_query->set_401();
status_header( 401 );
nocache_headers();
}
} );
my answer is a combination of : Redirect user using the ‘wp_login_failed’ action hook if the error is ’empty_username’ or ’empty_password’ and How to force a 404 on WordPress
update: I wrote super simple plugin to do this WP-401-On-Failed-Login. It uses some wp auth hooks, and set_header() before content being sent.