Skip to content

Commit 298799f

Browse files
authored
Merge pull request #160 from QuantGeekDev/fix/80-apikey-auth-fallbacks
fix: API key auth fallbacks for SSE clients without custom headers
2 parents 67494c1 + 8002267 commit 298799f

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

src/auth/providers/apikey.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ export interface APIKeyConfig {
1313
keys: string[];
1414

1515
/**
16-
* Name of the header containing the API key
16+
* Name of the header containing the API key.
17+
* Note: Some SSE client libraries (including the standard EventSource API)
18+
* do not support custom headers. For these clients, the API key can also
19+
* be sent as a query parameter: ?api_key=<key> or ?apiKey=<key>
1720
* @default "X-API-Key"
1821
*/
1922
headerName?: string;
@@ -78,8 +81,37 @@ export class APIKeyAuthProvider implements AuthProvider {
7881
}
7982
}
8083

84+
// Fallback: check Authorization Bearer header
85+
// Some clients (e.g., LangChain) may send API key as Bearer token
8186
if (!apiKey) {
82-
logger.debug(`API Key header missing}`);
87+
const authHeader = req.headers['authorization'];
88+
if (authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) {
89+
const bearerToken = authHeader.slice(7).trim();
90+
if (bearerToken) {
91+
apiKey = bearerToken;
92+
matchedHeader = 'Authorization Bearer';
93+
logger.debug('API key found in Authorization Bearer header (fallback)');
94+
}
95+
}
96+
}
97+
98+
// Fallback: check query parameter for SSE clients that cannot send custom headers
99+
if (!apiKey && req.url) {
100+
try {
101+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
102+
const queryKey = url.searchParams.get('api_key') || url.searchParams.get('apiKey');
103+
if (queryKey) {
104+
apiKey = queryKey;
105+
matchedHeader = 'query parameter';
106+
logger.debug('API key found in query parameter (EventSource fallback)');
107+
}
108+
} catch {
109+
// URL parsing failed, skip query parameter check
110+
}
111+
}
112+
113+
if (!apiKey) {
114+
logger.debug(`API Key header missing`);
83115
logger.debug(`Available headers: ${Object.keys(req.headers).join(', ')}`);
84116
return false;
85117
}

src/transports/utils/auth-handler.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,31 @@ export async function handleAuthentication(
2828

2929
const isApiKey = authConfig.provider instanceof APIKeyAuthProvider;
3030

31-
// Special handling for API Key - check header exists before authenticate
31+
// Special handling for API Key - check header/Bearer/query exists before authenticate
3232
if (isApiKey) {
3333
const provider = authConfig.provider as APIKeyAuthProvider;
3434
const headerValue = getRequestHeader(req.headers, provider.getHeaderName());
3535

36+
// Also check Authorization Bearer and query parameter as fallbacks
37+
// (for SSE clients like EventSource that can't send custom headers)
38+
let hasFallbackKey = false;
3639
if (!headerValue) {
40+
const authHeader = req.headers['authorization'];
41+
if (authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) {
42+
hasFallbackKey = true;
43+
}
44+
45+
if (!hasFallbackKey && req.url) {
46+
try {
47+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
48+
hasFallbackKey = !!(url.searchParams.get('api_key') || url.searchParams.get('apiKey'));
49+
} catch {
50+
// URL parsing failed
51+
}
52+
}
53+
}
54+
55+
if (!headerValue && !hasFallbackKey) {
3756
const error = provider.getAuthError?.() || DEFAULT_AUTH_ERROR;
3857
res.setHeader('WWW-Authenticate', `ApiKey realm="MCP Server", header="${provider.getHeaderName()}"`);
3958
res.writeHead(error.status).end(

0 commit comments

Comments
 (0)