I'd like to use basic auth with the WordPress API, but restrict the capability to a single IP address. I've updated the existing code from here: https://github.com/WP-API/Basic-Auth accordingly.
It seems to work fine but I would be interested in any comments about security or improvements that could be made.
class BasicAuth
{
public static function addAction(): void
{
$instance = new static;
add_filter('determine_current_user', [$instance, 'json_basic_auth_handler'], 20);
add_filter('rest_authentication_errors', [$instance, 'json_basic_auth_error'], 20);
}
protected $result;
/**
* Based on: https://github.com/WP-API/Basic-Auth
* @param int|bool $user
* @return int|bool|null $user
*/
function json_basic_auth_handler($user)
{
$this->result = null;
// Don't authenticate twice
if (!empty($user)) {
return $user;
}
// Check that we're trying to authenticate
if (!isset($_SERVER['PHP_AUTH_USER'])) {
return $user;
}
// Check client IP
if (!$this->checkIpPermitted()) {
return $user;
}
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
/**
* In multi-site, wp_authenticate_spam_check filter is run on authentication. This filter calls
* get_currentuserinfo which in turn calls the determine_current_user filter. This leads to infinite
* recursion and a stack overflow unless the current function is removed from the determine_current_user
* filter during authentication.
*/
remove_filter('determine_current_user', 'json_basic_auth_handler', 20);
$user = wp_authenticate($username, $password); /** @var WP_User|WP_Error $ */
add_filter('determine_current_user', 'json_basic_auth_handler', 20);
// Authentication failure
if (is_wp_error($user)) {
$this->result = $user;
return null;
}
// Authentication success
$this->result = true;
return $user->ID;
}
protected function checkIpPermitted(): bool
{
$options = get_option(OptionsPage::CONNECT_OPTIONS);
$ip = $options['ip'] ?? '';
if (($_SERVER['REMOTE_ADDR'] ?? null) !== $ip) {
$this->result = new WP_Error('ip-blocked', 'Invalid request');
return false;
}
return true;
}
function json_basic_auth_error($error)
{
// Passthrough other errors
if (!empty($error)) {
return $error;
}
// WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
return $this->result;
}
}