Skip to content

Commit d16ebfa

Browse files
committed
fix(client): refresh challenged discovery before AS binding
1 parent 4dc5346 commit d16ebfa

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

packages/client/src/client/auth.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -632,14 +632,15 @@ async function authInternal(
632632
let authorizationServerUrl: string | URL;
633633
let metadata: AuthorizationServerMetadata | undefined;
634634

635-
// If resourceMetadataUrl is not provided, try to load it from cached state
636-
// This handles browser redirects where the URL was saved before navigation
635+
// If resourceMetadataUrl is not provided, try to load it from cached state.
636+
// This handles browser redirects where the URL was saved before navigation.
637637
let effectiveResourceMetadataUrl = resourceMetadataUrl;
638638
if (!effectiveResourceMetadataUrl && cachedState?.resourceMetadataUrl) {
639639
effectiveResourceMetadataUrl = new URL(cachedState.resourceMetadataUrl);
640640
}
641+
const shouldRefreshCachedDiscovery = cachedState?.authorizationServerUrl !== undefined && resourceMetadataUrl !== undefined;
641642

642-
if (cachedState?.authorizationServerUrl) {
643+
if (cachedState?.authorizationServerUrl && !shouldRefreshCachedDiscovery) {
643644
// Restore discovery state from cache
644645
authorizationServerUrl = cachedState.authorizationServerUrl;
645646
resourceMetadata = cachedState.resourceMetadata;

packages/client/test/client/auth.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4289,6 +4289,54 @@ describe('SEP-2352: authorization server binding', () => {
42894289
expect(redirectUrl.searchParams.get('client_id')).toBe('new-client-id');
42904290
});
42914291

4292+
it('refreshes cached discovery from an explicit resource metadata challenge before comparing authorization servers', async () => {
4293+
const { provider, invalidateCredentials, saveClientInformation, redirectToAuthorization } = createBoundProvider({
4294+
client_id: 'old-client-id',
4295+
client_secret: 'old-client-secret'
4296+
});
4297+
const resourceMetadataUrl = new URL('https://resource.example.com/.well-known/oauth-protected-resource');
4298+
4299+
provider.discoveryState = vi.fn().mockResolvedValue({
4300+
authorizationServerUrl: oldAuthServerUrl,
4301+
resourceMetadata: sameResourceMetadata,
4302+
authorizationServerMetadata: sameAuthMetadata
4303+
});
4304+
provider.saveDiscoveryState = vi.fn();
4305+
4306+
mockDiscoveryAndRegistration({
4307+
resourceMetadata: newResourceMetadata,
4308+
authMetadata: newAuthMetadata,
4309+
registeredClient: { client_id: 'new-client-id', client_secret: 'new-client-secret' }
4310+
});
4311+
4312+
const result = await auth(provider, {
4313+
serverUrl: 'https://resource.example.com',
4314+
resourceMetadataUrl
4315+
});
4316+
4317+
expect(result).toBe('REDIRECT');
4318+
4319+
const prmCalls = mockFetch.mock.calls.filter(call => call[0].toString().includes('oauth-protected-resource'));
4320+
expect(prmCalls).toHaveLength(1);
4321+
expect(prmCalls[0]![0].toString()).toBe(resourceMetadataUrl.toString());
4322+
4323+
expect(provider.saveDiscoveryState).toHaveBeenCalledWith(
4324+
expect.objectContaining({
4325+
authorizationServerUrl: 'https://new-auth.example.com',
4326+
resourceMetadataUrl: resourceMetadataUrl.toString(),
4327+
resourceMetadata: newResourceMetadata,
4328+
authorizationServerMetadata: newAuthMetadata
4329+
})
4330+
);
4331+
expect(invalidateCredentials).toHaveBeenCalledWith('client');
4332+
expect(invalidateCredentials).toHaveBeenCalledWith('tokens');
4333+
expect(saveClientInformation).toHaveBeenCalledWith(expect.objectContaining({ client_id: 'new-client-id' }));
4334+
4335+
const redirectUrl: URL = redirectToAuthorization.mock.calls[0]![0];
4336+
expect(redirectUrl.origin).toBe('https://new-auth.example.com');
4337+
expect(redirectUrl.searchParams.get('client_id')).toBe('new-client-id');
4338+
});
4339+
42924340
it('does not invalidate credentials when the authorization server is unchanged', async () => {
42934341
const { provider, invalidateCredentials, redirectToAuthorization } = createBoundProvider({
42944342
client_id: 'old-client-id',

0 commit comments

Comments
 (0)