fix: add Bearer token auth for Claude.ai connector compatibility
This commit is contained in:
+44
-23
@@ -5,8 +5,13 @@ namespace AskTamMCP\Core;
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Authenticates incoming requests by API key.
|
||||
* Validates key existence, active status, and scope.
|
||||
* Authenticates incoming requests.
|
||||
*
|
||||
* Supports two auth methods:
|
||||
* 1. Bearer token (Authorization: Bearer <key>) — for Claude.ai connectors + standard OAuth clients
|
||||
* 2. X-MCP-API-Key header — for direct API / OpenAI GPT Actions
|
||||
*
|
||||
* Both resolve to the same DB key row. Bearer is checked first.
|
||||
*/
|
||||
class Auth {
|
||||
|
||||
@@ -16,17 +21,18 @@ class Auth {
|
||||
public const SCOPE_WOOCOMMERCE = 'woocommerce';
|
||||
|
||||
/**
|
||||
* Validate the API key from request headers and check required scope.
|
||||
*
|
||||
* @param \WP_REST_Request $request
|
||||
* @param string $requiredScope
|
||||
* @return \WP_Error|array WP_Error on failure, key row array on success
|
||||
* Validate request auth and check required scope.
|
||||
* Returns the key row on success, WP_Error on failure.
|
||||
*/
|
||||
public static function validate(\WP_REST_Request $request, string $requiredScope = self::SCOPE_READ): \WP_Error|array {
|
||||
$apiKey = $request->get_header('X-MCP-API-Key');
|
||||
$apiKey = self::extractKey($request);
|
||||
|
||||
if (empty($apiKey)) {
|
||||
return new \WP_Error('missing_api_key', 'X-MCP-API-Key header is required.', ['status' => 401]);
|
||||
return new \WP_Error(
|
||||
'missing_api_key',
|
||||
'Authentication required. Provide key via: Authorization: Bearer <key> or X-MCP-API-Key: <key>',
|
||||
['status' => 401]
|
||||
);
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
@@ -40,33 +46,28 @@ class Auth {
|
||||
return new \WP_Error('invalid_api_key', 'Invalid or inactive API key.', ['status' => 401]);
|
||||
}
|
||||
|
||||
// Rate limiting — transient per key, per minute
|
||||
$rateLimitKey = 'asktam_mcp_rl_' . md5($apiKey);
|
||||
$settings = get_option('asktam_mcp_settings', []);
|
||||
$limit = (int) ($settings['rate_limit'] ?? 60);
|
||||
$currentCount = (int) get_transient($rateLimitKey);
|
||||
// Rate limiting — transient per key per minute
|
||||
$rlKey = 'asktam_mcp_rl_' . md5($apiKey);
|
||||
$settings = get_option('asktam_mcp_settings', []);
|
||||
$limit = (int) ($settings['rate_limit'] ?? 60);
|
||||
$currentCount = (int) get_transient($rlKey);
|
||||
|
||||
if ($currentCount >= $limit) {
|
||||
return new \WP_Error('rate_limit_exceeded', 'Rate limit exceeded. Retry in 60 seconds.', ['status' => 429]);
|
||||
}
|
||||
set_transient($rlKey, $currentCount + 1, 60);
|
||||
|
||||
if ($currentCount === 0) {
|
||||
set_transient($rateLimitKey, 1, 60);
|
||||
} else {
|
||||
set_transient($rateLimitKey, $currentCount + 1, 60);
|
||||
}
|
||||
|
||||
// Scope check — 'admin' keys get all scopes
|
||||
// Scope check — admin keys bypass all scope requirements
|
||||
$scopes = array_map('trim', explode(',', $row['scopes']));
|
||||
if (!in_array($requiredScope, $scopes, true) && !in_array(self::SCOPE_ADMIN, $scopes, true)) {
|
||||
return new \WP_Error(
|
||||
'insufficient_scope',
|
||||
"This key does not have the required scope: {$requiredScope}",
|
||||
"Insufficient scope. Required: {$requiredScope}",
|
||||
['status' => 403]
|
||||
);
|
||||
}
|
||||
|
||||
// Update last_used timestamp (async-ish via shutdown)
|
||||
// Record last_used on shutdown
|
||||
add_action('shutdown', function () use ($wpdb, $table, $apiKey): void {
|
||||
$wpdb->update($table, ['last_used' => current_time('mysql')], ['api_key' => $apiKey]);
|
||||
});
|
||||
@@ -74,6 +75,26 @@ class Auth {
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract API key from request.
|
||||
* Checks: Authorization: Bearer <key> then X-MCP-API-Key header.
|
||||
*/
|
||||
private static function extractKey(\WP_REST_Request $request): string {
|
||||
// 1. Bearer token — Claude.ai connectors use this
|
||||
$authHeader = $request->get_header('Authorization');
|
||||
if ($authHeader && str_starts_with($authHeader, 'Bearer ')) {
|
||||
return trim(substr($authHeader, 7));
|
||||
}
|
||||
|
||||
// 2. X-MCP-API-Key — OpenAI GPT Actions + direct use
|
||||
$xKey = $request->get_header('X-MCP-API-Key');
|
||||
if ($xKey) {
|
||||
return trim($xKey);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure API key.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user