From d89e85413303896f768cc9d44203515b129cba91 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Fri, 20 Jun 2025 12:54:29 -0700 Subject: [PATCH] fix(client/sse): extract protected resource from eventsource 401 Previously the SSE connection would always default to the `/.well-known/oauth-protected-resource` URI, ignoring the `resource_metadata` portion of the `www-authenticate` returned in a 401. Extract the metadata from the initial 401, so RS servers with custom protected resource URIs (as in RFC9728, [section 3.1][1])) continue to work as expected. [1]: https://datatracker.ietf.org/doc/html/rfc9728#section-3.1 --- src/client/sse.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/client/sse.ts b/src/client/sse.ts index 5aa99abb..2546d508 100644 --- a/src/client/sse.ts +++ b/src/client/sse.ts @@ -117,23 +117,35 @@ export class SSEClientTransport implements Transport { } private _startOrAuth(): Promise { + const fetchImpl = (this?._eventSourceInit?.fetch || fetch) as typeof fetch return new Promise((resolve, reject) => { this._eventSource = new EventSource( this._url.href, - this._eventSourceInit ?? { - fetch: (url, init) => this._commonHeaders().then((headers) => fetch(url, { - ...init, - headers: { - ...headers, - Accept: "text/event-stream" + { + ...this._eventSourceInit, + fetch: async (url, init) => { + const headers = await this._commonHeaders() + const response = await fetchImpl(url, { + ...init, + headers: new Headers({ + ...headers, + Accept: "text/event-stream" + }) + }) + + if (response.status === 401 && response.headers.has('www-authenticate')) { + this._resourceMetadataUrl = extractResourceMetadataUrl(response); } - })), + + return response + }, }, ); this._abortController = new AbortController(); this._eventSource.onerror = (event) => { if (event.code === 401 && this._authProvider) { + this._authThenStart().then(resolve, reject); return; }