diff --git a/packages/core/src/libraries/sso-connector.test.ts b/packages/core/src/libraries/sso-connector.test.ts index 763572641f7..3047323ac14 100644 --- a/packages/core/src/libraries/sso-connector.test.ts +++ b/packages/core/src/libraries/sso-connector.test.ts @@ -1,4 +1,4 @@ -import { Prompt, QueryKey, withReservedScopes } from '@logto/js'; +import { Prompt, QueryKey, ReservedScope, UserScope } from '@logto/js'; import { ApplicationType, type SsoConnectorIdpInitiatedAuthConfig } from '@logto/schemas'; import { mockSsoConnector, wellConfiguredSsoConnector } from '#src/__mocks__/sso.js'; @@ -204,7 +204,7 @@ describe('SsoConnectorLibrary', () => { for (const [key, value] of Object.entries(defaultQueryParameters)) { expect(parameters.get(key)).toBe(value); } - expect(parameters.get(QueryKey.Scope)).toBe(withReservedScopes()); + expect(parameters.get(QueryKey.Scope)).toBe(`${ReservedScope.OpenId} ${UserScope.Profile}`); }); it('should use the provided redirectUri', async () => { @@ -233,7 +233,7 @@ describe('SsoConnectorLibrary', () => { }); it('should append extra scopes to the query parameters', async () => { - const scopes = ['scope1', 'scope2']; + const scopes = ['organization', 'email', 'profile']; const url = await getIdpInitiatedSamlSsoSignInUrl(issuer, { ...authConfig, @@ -243,7 +243,9 @@ describe('SsoConnectorLibrary', () => { }); const parameters = new URLSearchParams(url.search); - expect(parameters.get(QueryKey.Scope)).toBe(withReservedScopes(scopes)); + expect(parameters.get(QueryKey.Scope)).toBe( + `${ReservedScope.OpenId} ${UserScope.Profile} organization email` + ); }); it('should be able to append extra query parameters', async () => { diff --git a/packages/core/src/libraries/sso-connector.ts b/packages/core/src/libraries/sso-connector.ts index 6e35e12de91..40aea5f10c9 100644 --- a/packages/core/src/libraries/sso-connector.ts +++ b/packages/core/src/libraries/sso-connector.ts @@ -1,4 +1,4 @@ -import { type DirectSignInOptions, Prompt, QueryKey, withReservedScopes } from '@logto/js'; +import { type DirectSignInOptions, Prompt, QueryKey, ReservedScope, UserScope } from '@logto/js'; import { ApplicationType, type SsoSamlAssertionContent, @@ -7,7 +7,7 @@ import { type SsoConnectorIdpInitiatedAuthConfig, } from '@logto/schemas'; import { generateStandardId } from '@logto/shared'; -import { assert, trySafe } from '@silverhand/essentials'; +import { assert, deduplicate, trySafe } from '@silverhand/essentials'; import { defaultIdPInitiatedSamlSsoSessionTtl } from '#src/constants/index.js'; import RequestError from '#src/errors/RequestError/index.js'; @@ -141,6 +141,9 @@ export const createSsoConnectorLibrary = (queries: Queries) => { * * @remarks * For IdP-initiated SAML SSO flow use only. Generate the sign-in URL for the user to sign in. + * Default scopes: openid, profile + * Default prompt: login + * Default response type: code * * @param issuer The oidc issuer endpoint of the current tenant. * @param authConfig The IdP-initiated SAML SSO authentication configuration. @@ -183,7 +186,11 @@ export const createSsoConnectorLibrary = (queries: Queries) => { ...extraParams, }); - queryParameters.append(QueryKey.Scope, withReservedScopes(scope?.split(' ') ?? [])); + queryParameters.append( + QueryKey.Scope, + // For security reasons, DO NOT include the offline_access scope for IdP-initiated SAML SSO by default + deduplicate([ReservedScope.OpenId, UserScope.Profile, ...(scope?.split(' ') ?? [])]).join(' ') + ); return new URL(`${issuer}/auth?${queryParameters.toString()}`); };