tigerstyle-scent/Auth/ApiKeyAuthenticator.php
Ryan Malloy 1da0acd25a Initial commit: WordPress OAuth2 Server with PSR-4 architecture
- Implements complete OAuth2 authorization server for WordPress
- PSR-4 autoloading with WPOAuth2Server namespace structure
- Modular architecture with Auth, Client, Core, Storage components
- Successfully tested authorization code flow with bearer authentication
- Clean separation from WordPress plugin code for reusability
2025-09-16 20:53:00 -06:00

193 lines
5.6 KiB
PHP

<?php
/**
* API Key Authenticator
* Implements API key authentication for WordPress
*
* @package WordPress Authentication Framework PoC
*/
namespace WPOAuth2Server\Auth;
defined('ABSPATH') or die('Direct access forbidden.');
class ApiKeyAuthenticator implements AuthenticatorInterface {
/**
* API key storage option name
*/
private const API_KEY_STORAGE_OPTION = 'wp_auth_framework_api_keys';
public function get_name(): string {
return 'api_key';
}
public function get_display_name(): string {
return __('API Key Authentication', 'wp-oauth2-poc');
}
public function get_description(): string {
return __('Authenticates users using API keys passed in X-API-Key header or api_key parameter', 'wp-oauth2-poc');
}
public function can_authenticate(): bool {
return $this->get_api_key() !== null;
}
public function authenticate(): ?int {
$api_key = $this->get_api_key();
if (!$api_key) {
return null;
}
$user_id = $this->validate_api_key($api_key);
if (!$user_id) {
throw new \Exception('Invalid API key');
}
// Verify user still exists and is active
$user = get_user_by('ID', $user_id);
if (!$user) {
throw new \Exception('User account not found');
}
return (int) $user_id;
}
public function validate_credentials(): bool {
$api_key = $this->get_api_key();
return $api_key && $this->validate_api_key($api_key) !== null;
}
public function get_priority(): int {
return 20; // Lower priority than OAuth2
}
public function requires_https(): bool {
return true; // API keys should require HTTPS
}
public function get_allowed_methods(): array {
return ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
}
public function get_response_headers(): array {
return [];
}
/**
* Get API key from request (header or parameter)
*/
private function get_api_key(): ?string {
// Check X-API-Key header
if (isset($_SERVER['HTTP_X_API_KEY'])) {
return sanitize_text_field($_SERVER['HTTP_X_API_KEY']);
}
// Check api_key parameter
if (isset($_GET['api_key'])) {
return sanitize_text_field($_GET['api_key']);
}
if (isset($_POST['api_key'])) {
return sanitize_text_field($_POST['api_key']);
}
return null;
}
/**
* Validate API key and return user ID
*/
private function validate_api_key(string $api_key): ?int {
$stored_keys = get_option(self::API_KEY_STORAGE_OPTION, []);
foreach ($stored_keys as $key_data) {
if (hash_equals($key_data['key'], $api_key)) {
// Check if key is active
if (!$key_data['active']) {
continue;
}
// Check expiration if set
if (isset($key_data['expires']) && time() > $key_data['expires']) {
continue;
}
// Update last used timestamp
$this->update_last_used($api_key);
return (int) $key_data['user_id'];
}
}
return null;
}
/**
* Update last used timestamp for API key
*/
private function update_last_used(string $api_key): void {
$stored_keys = get_option(self::API_KEY_STORAGE_OPTION, []);
foreach ($stored_keys as &$key_data) {
if (hash_equals($key_data['key'], $api_key)) {
$key_data['last_used'] = time();
break;
}
}
update_option(self::API_KEY_STORAGE_OPTION, $stored_keys);
}
/**
* Generate a new API key for a user
*/
public static function generate_api_key_for_user(int $user_id, string $name = '', ?int $expires_in = null): string {
$api_key = 'wpak_' . bin2hex(random_bytes(24)); // 48 character key with prefix
$expires = $expires_in ? time() + $expires_in : null;
$stored_keys = get_option(self::API_KEY_STORAGE_OPTION, []);
$stored_keys[] = [
'key' => $api_key,
'user_id' => $user_id,
'name' => $name ?: 'API Key ' . date('Y-m-d H:i:s'),
'created' => time(),
'expires' => $expires,
'last_used' => null,
'active' => true,
];
update_option(self::API_KEY_STORAGE_OPTION, $stored_keys);
return $api_key;
}
/**
* Revoke an API key
*/
public static function revoke_api_key(string $api_key): bool {
$stored_keys = get_option(self::API_KEY_STORAGE_OPTION, []);
foreach ($stored_keys as &$key_data) {
if (hash_equals($key_data['key'], $api_key)) {
$key_data['active'] = false;
$key_data['revoked'] = time();
update_option(self::API_KEY_STORAGE_OPTION, $stored_keys);
return true;
}
}
return false;
}
/**
* List API keys for a user
*/
public static function get_user_api_keys(int $user_id): array {
$stored_keys = get_option(self::API_KEY_STORAGE_OPTION, []);
return array_filter($stored_keys, function($key_data) use ($user_id) {
return $key_data['user_id'] === $user_id && $key_data['active'];
});
}
}