From 618f24a9acb25222b01dec19e773362bb36c7abb Mon Sep 17 00:00:00 2001 From: xiaoli Date: Tue, 13 May 2025 23:38:19 +0800 Subject: [PATCH 1/6] feat: support custom authorization headers, fix #395 --- client/src/lib/hooks/useConnection.ts | 7 ++++++- server/src/index.ts | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 4ced90e4..501c8359 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -297,7 +297,12 @@ export function useConnection({ bearerToken || (await serverAuthProvider.tokens())?.access_token; if (token) { const authHeaderName = headerName || "Authorization"; - headers[authHeaderName] = `Bearer ${token}`; + headers[authHeaderName] = token; + + // Add custom header name as a special request header to let the server know which header to pass through + if (headerName && headerName.toLowerCase() !== "authorization") { + headers["x-custom-auth-header"] = headerName; + } } // Create appropriate transport diff --git a/server/src/index.ts b/server/src/index.ts index c967b60c..766d2df4 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -91,6 +91,15 @@ const createTransport = async (req: express.Request): Promise => { const value = req.headers[key]; headers[key] = Array.isArray(value) ? value[value.length - 1] : value; } + + // If the header "x-custom-auth-header" is present, use its value as the custom header name. + if (req.headers["x-custom-auth-header"] !== undefined) { + const customHeaderName = req.headers["x-custom-auth-header"] as string; + if (req.headers[customHeaderName.toLowerCase()] !== undefined) { + const value = req.headers[customHeaderName.toLowerCase()]; + headers[customHeaderName] = Array.isArray(value) ? value[value.length - 1] : value as string; + } + } console.log(`SSE transport: url=${url}, headers=${Object.keys(headers)}`); @@ -119,6 +128,15 @@ const createTransport = async (req: express.Request): Promise => { const value = req.headers[key]; headers[key] = Array.isArray(value) ? value[value.length - 1] : value; } + + // If the header "x-custom-auth-header" is present, use its value as the custom header name. + if (req.headers["x-custom-auth-header"] !== undefined) { + const customHeaderName = req.headers["x-custom-auth-header"] as string; + if (req.headers[customHeaderName.toLowerCase()] !== undefined) { + const value = req.headers[customHeaderName.toLowerCase()]; + headers[customHeaderName] = Array.isArray(value) ? value[value.length - 1] : value as string; + } + } const transport = new StreamableHTTPClientTransport( new URL(query.url as string), From 13f736033a98b4502af2b7d47cf630fde1e72b3a Mon Sep 17 00:00:00 2001 From: xiaoli Date: Tue, 20 May 2025 13:10:55 +0800 Subject: [PATCH 2/6] fix: fix review comments - Avoid calling .lowerCase twice - Keep 'Bearer ' prefix when header equals 'Authorization' - Remove unnecessary Array type checks --- client/src/lib/hooks/useConnection.ts | 10 +++++++--- server/src/index.ts | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 501c8359..f1f7ab9e 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -297,11 +297,15 @@ export function useConnection({ bearerToken || (await serverAuthProvider.tokens())?.access_token; if (token) { const authHeaderName = headerName || "Authorization"; - headers[authHeaderName] = token; // Add custom header name as a special request header to let the server know which header to pass through - if (headerName && headerName.toLowerCase() !== "authorization") { - headers["x-custom-auth-header"] = headerName; + if (authHeaderName.toLowerCase() !== "authorization") { + headers[authHeaderName] = token; + if (headerName) { + headers["x-custom-auth-header"] = headerName; + } + } else { + headers[authHeaderName] = `Bearer ${token}`; } } diff --git a/server/src/index.ts b/server/src/index.ts index 766d2df4..fd28a807 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -95,9 +95,9 @@ const createTransport = async (req: express.Request): Promise => { // If the header "x-custom-auth-header" is present, use its value as the custom header name. if (req.headers["x-custom-auth-header"] !== undefined) { const customHeaderName = req.headers["x-custom-auth-header"] as string; - if (req.headers[customHeaderName.toLowerCase()] !== undefined) { - const value = req.headers[customHeaderName.toLowerCase()]; - headers[customHeaderName] = Array.isArray(value) ? value[value.length - 1] : value as string; + if (req.headers[customHeaderName] !== undefined) { + const value = req.headers[customHeaderName]; + headers[customHeaderName] = value as string; } } @@ -132,9 +132,9 @@ const createTransport = async (req: express.Request): Promise => { // If the header "x-custom-auth-header" is present, use its value as the custom header name. if (req.headers["x-custom-auth-header"] !== undefined) { const customHeaderName = req.headers["x-custom-auth-header"] as string; - if (req.headers[customHeaderName.toLowerCase()] !== undefined) { - const value = req.headers[customHeaderName.toLowerCase()]; - headers[customHeaderName] = Array.isArray(value) ? value[value.length - 1] : value as string; + if (req.headers[customHeaderName] !== undefined) { + const value = req.headers[customHeaderName]; + headers[customHeaderName] = value as string; } } From 2870441fea8206625142f9d6b15ffe9904b82377 Mon Sep 17 00:00:00 2001 From: xiaoli Date: Sun, 25 May 2025 11:17:37 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20fix=20review=20comments,=20again?= =?UTF-8?q?=F0=9F=98=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary auth header name check - Extract operations on headers into a function --- client/src/lib/hooks/useConnection.ts | 3 - server/src/index.ts | 80 +++++++++++++-------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index f1f7ab9e..84956942 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -301,9 +301,6 @@ export function useConnection({ // Add custom header name as a special request header to let the server know which header to pass through if (authHeaderName.toLowerCase() !== "authorization") { headers[authHeaderName] = token; - if (headerName) { - headers["x-custom-auth-header"] = headerName; - } } else { headers[authHeaderName] = `Bearer ${token}`; } diff --git a/server/src/index.ts b/server/src/index.ts index fd28a807..de14e286 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -41,6 +41,43 @@ const { values } = parseArgs({ }, }); +// Function to get HTTP headers. +// Supports only "sse" and "streamable-http" transport types. +const getHttpHeaders = ( + req: express.Request, + transportType: string, +): HeadersInit => { + const headers: HeadersInit = { + Accept: + transportType === "sse" + ? "text/event-stream" + : "text/event-stream, application/json", + }; + const defaultHeaders = + transportType === "sse" + ? SSE_HEADERS_PASSTHROUGH + : STREAMABLE_HTTP_HEADERS_PASSTHROUGH; + + for (const key of defaultHeaders) { + if (req.headers[key] === undefined) { + continue; + } + + const value = req.headers[key]; + headers[key] = Array.isArray(value) ? value[value.length - 1] : value; + } + + // If the header "x-custom-auth-header" is present, use its value as the custom header name. + if (req.headers["x-custom-auth-header"] !== undefined) { + const customHeaderName = req.headers["x-custom-auth-header"] as string; + if (req.headers[customHeaderName] !== undefined) { + const value = req.headers[customHeaderName]; + headers[customHeaderName] = value as string; + } + } + return headers; +}; + const app = express(); app.use(cors()); app.use((req, res, next) => { @@ -79,27 +116,8 @@ const createTransport = async (req: express.Request): Promise => { return transport; } else if (transportType === "sse") { const url = query.url as string; - const headers: HeadersInit = { - Accept: "text/event-stream", - }; - for (const key of SSE_HEADERS_PASSTHROUGH) { - if (req.headers[key] === undefined) { - continue; - } - - const value = req.headers[key]; - headers[key] = Array.isArray(value) ? value[value.length - 1] : value; - } - - // If the header "x-custom-auth-header" is present, use its value as the custom header name. - if (req.headers["x-custom-auth-header"] !== undefined) { - const customHeaderName = req.headers["x-custom-auth-header"] as string; - if (req.headers[customHeaderName] !== undefined) { - const value = req.headers[customHeaderName]; - headers[customHeaderName] = value as string; - } - } + const headers = getHttpHeaders(req, transportType); console.log(`SSE transport: url=${url}, headers=${Object.keys(headers)}`); @@ -116,27 +134,7 @@ const createTransport = async (req: express.Request): Promise => { console.log("Connected to SSE transport"); return transport; } else if (transportType === "streamable-http") { - const headers: HeadersInit = { - Accept: "text/event-stream, application/json", - }; - - for (const key of STREAMABLE_HTTP_HEADERS_PASSTHROUGH) { - if (req.headers[key] === undefined) { - continue; - } - - const value = req.headers[key]; - headers[key] = Array.isArray(value) ? value[value.length - 1] : value; - } - - // If the header "x-custom-auth-header" is present, use its value as the custom header name. - if (req.headers["x-custom-auth-header"] !== undefined) { - const customHeaderName = req.headers["x-custom-auth-header"] as string; - if (req.headers[customHeaderName] !== undefined) { - const value = req.headers[customHeaderName]; - headers[customHeaderName] = value as string; - } - } + const headers = getHttpHeaders(req, transportType); const transport = new StreamableHTTPClientTransport( new URL(query.url as string), From 10e3202832c51add7e0fdb8ab16cecde2646c47e Mon Sep 17 00:00:00 2001 From: xiaoli Date: Thu, 29 May 2025 11:49:57 +0800 Subject: [PATCH 4/6] fix: Fix the issue of accidentally losing 'x-custom-auth-header' --- client/src/lib/hooks/useConnection.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 84956942..f1c29b2d 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -301,6 +301,7 @@ export function useConnection({ // Add custom header name as a special request header to let the server know which header to pass through if (authHeaderName.toLowerCase() !== "authorization") { headers[authHeaderName] = token; + headers["x-custom-auth-header"] = authHeaderName; } else { headers[authHeaderName] = `Bearer ${token}`; } From b51ee625cddcf76608873c381c069a07195ace73 Mon Sep 17 00:00:00 2001 From: xiaoli Date: Fri, 30 May 2025 10:14:03 +0800 Subject: [PATCH 5/6] style: fix code style to pass CI checks --- client/src/lib/hooks/useConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index fae904da..e4f1bc50 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -304,8 +304,8 @@ export function useConnection({ bearerToken || (await serverAuthProvider.tokens())?.access_token; if (token) { const authHeaderName = headerName || "Authorization"; - - // Add custom header name as a special request header to let the server know which header to pass through + + // Add custom header name as a special request header to let the server know which header to pass through if (authHeaderName.toLowerCase() !== "authorization") { headers[authHeaderName] = token; headers["x-custom-auth-header"] = authHeaderName; From 3510e1a56128a094638290531fec59fc820ffa2e Mon Sep 17 00:00:00 2001 From: xiaoli Date: Fri, 30 May 2025 14:43:09 +0800 Subject: [PATCH 6/6] fix: preserve capitalization in custom headers Express converts all header names to lowercase, which prevents custom headers with uppercase characters from being properly passed to the target service. This fix ensures that custom headers preserve their original capitalization when forwarded through the proxy. --- server/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index de14e286..962c90e8 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -70,8 +70,9 @@ const getHttpHeaders = ( // If the header "x-custom-auth-header" is present, use its value as the custom header name. if (req.headers["x-custom-auth-header"] !== undefined) { const customHeaderName = req.headers["x-custom-auth-header"] as string; - if (req.headers[customHeaderName] !== undefined) { - const value = req.headers[customHeaderName]; + const lowerCaseHeaderName = customHeaderName.toLowerCase(); + if (req.headers[lowerCaseHeaderName] !== undefined) { + const value = req.headers[lowerCaseHeaderName]; headers[customHeaderName] = value as string; } }