From e333dd769a5b6cdd246c6647d30c61716ac86e5f Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Thu, 21 May 2026 00:34:29 +0800 Subject: [PATCH] fix: continue OAuth discovery after non-JSON metadata --- packages/client/src/client/auth.ts | 11 ++++++++--- packages/client/test/client/auth.test.ts | 25 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 5f55fb7a08..1c88482d43 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1262,10 +1262,15 @@ export async function discoverAuthorizationServerMetadata( ); } + let metadata: unknown; + try { + metadata = await response.json(); + } catch { + continue; + } + // Parse and validate based on type - return type === 'oauth' - ? OAuthMetadataSchema.parse(await response.json()) - : OpenIdProviderDiscoveryMetadataSchema.parse(await response.json()); + return type === 'oauth' ? OAuthMetadataSchema.parse(metadata) : OpenIdProviderDiscoveryMetadataSchema.parse(metadata); } return undefined; diff --git a/packages/client/test/client/auth.test.ts b/packages/client/test/client/auth.test.ts index 04d7f4a3fb..2219ae2bb6 100644 --- a/packages/client/test/client/auth.test.ts +++ b/packages/client/test/client/auth.test.ts @@ -924,6 +924,31 @@ describe('OAuth Authorization', () => { expect(metadata).toEqual(validOpenIdMetadata); }); + it('continues when a successful metadata response is not JSON', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => { + throw new SyntaxError('Unexpected token <'); + } + }); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => validOpenIdMetadata + }); + + const metadata = await discoverAuthorizationServerMetadata('https://auth.example.com/tenant1'); + + expect(metadata).toEqual(validOpenIdMetadata); + + const calls = mockFetch.mock.calls; + expect(calls.length).toBe(2); + expect(calls[0]![0].toString()).toBe('https://auth.example.com/.well-known/oauth-authorization-server/tenant1'); + expect(calls[1]![0].toString()).toBe('https://auth.example.com/.well-known/openid-configuration/tenant1'); + }); + it('continues on 502 and tries next URL', async () => { // First URL (OAuth) returns 502 (reverse proxy with no route) mockFetch.mockResolvedValueOnce({