diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 5f55fb7a0..5c26490fb 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1420,7 +1420,11 @@ export async function startAuthorization( // if the request includes the OIDC-only "offline_access" scope, // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess - authorizationUrl.searchParams.append('prompt', 'consent'); + // Use .set() not .append(): RFC 6749 §3.1 forbids duplicate params, and the + // authorization_endpoint may already include a prompt value. Azure AD also rejects the + // OIDC space-delimited list form (AADSTS90023), so a single value is the only portable + // option; consent is required for offline_access per OIDC §11. + authorizationUrl.searchParams.set('prompt', 'consent'); } if (resource) { diff --git a/packages/client/test/client/auth.test.ts b/packages/client/test/client/auth.test.ts index 04d7f4a3f..80d28b057 100644 --- a/packages/client/test/client/auth.test.ts +++ b/packages/client/test/client/auth.test.ts @@ -1557,6 +1557,23 @@ describe('OAuth Authorization', () => { expect(authorizationUrl.searchParams.get('prompt')).toBe('consent'); }); + it('overwrites existing prompt parameter from authorization_endpoint instead of duplicating', async () => { + const { authorizationUrl } = await startAuthorization('https://auth.example.com', { + metadata: { + issuer: 'https://auth.example.com', + authorization_endpoint: 'https://auth.example.com/authorize?prompt=select_account', + token_endpoint: 'https://auth.example.com/token', + response_types_supported: ['code'], + code_challenge_methods_supported: ['S256'] + }, + clientInformation: validClientInfo, + redirectUrl: 'http://localhost:3000/callback', + scope: 'read offline_access' + }); + + expect(authorizationUrl.searchParams.getAll('prompt')).toEqual(['consent']); + }); + it.each([validMetadata, validOpenIdMetadata])('uses metadata authorization_endpoint when provided', async baseMetadata => { const { authorizationUrl } = await startAuthorization('https://auth.example.com', { metadata: baseMetadata,