php-mcp/src/Mod/Mcp/Middleware/McpApiKeyAuth.php
2026-01-26 20:57:41 +00:00

85 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mod\Mcp\Middleware;
use Core\Mod\Api\Models\ApiKey;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* API Key authentication for MCP HTTP API.
*
* Supports:
* - Authorization: Bearer hk_xxx_yyy
* - X-API-Key: hk_xxx_yyy
*
* Also enforces per-server access scopes on tool execution.
*/
class McpApiKeyAuth
{
public function handle(Request $request, Closure $next): Response
{
$key = $this->extractKey($request);
if (! $key) {
return response()->json([
'error' => 'Missing API key',
'hint' => 'Provide via Authorization: Bearer <key> or X-API-Key header',
], 401);
}
$apiKey = ApiKey::findByPlainKey($key);
if (! $apiKey) {
return response()->json([
'error' => 'Invalid API key',
], 401);
}
if ($apiKey->isExpired()) {
return response()->json([
'error' => 'API key has expired',
], 401);
}
// Check server-level access for tool calls
if ($request->is('*/tools/call') && $request->isMethod('POST')) {
$serverId = $request->input('server');
if ($serverId && ! $apiKey->hasServerAccess($serverId)) {
return response()->json([
'error' => 'Access denied to server: '.$serverId,
'allowed_servers' => $apiKey->getAllowedServers(),
], 403);
}
}
// Record usage
$apiKey->recordUsage();
// Attach to request for controller access
$request->attributes->set('api_key', $apiKey);
$request->attributes->set('workspace', $apiKey->workspace);
return $next($request);
}
protected function extractKey(Request $request): ?string
{
// Try Authorization: Bearer
$authHeader = $request->header('Authorization');
if ($authHeader && str_starts_with($authHeader, 'Bearer ')) {
return substr($authHeader, 7);
}
// Try X-API-Key
$apiKeyHeader = $request->header('X-API-Key');
if ($apiKeyHeader) {
return $apiKeyHeader;
}
return null;
}
}