From 742e5ef3a19982057b8e72a467a16f9567fdaa41 Mon Sep 17 00:00:00 2001 From: Stacy Curry Date: Tue, 15 Jul 2025 18:18:42 -0500 Subject: [PATCH 1/2] use qs to create query strings for getAuthorizationUrl --- package.json | 3 ++- src/user-management/user-management.ts | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 538abc101..ddd46b88f 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "iron-session": "~6.3.1", "jose": "~5.6.3", "leb": "^1.0.0", - "pluralize": "8.0.0" + "pluralize": "8.0.0", + "qs": "6.14.0" }, "devDependencies": { "@peculiar/webcrypto": "^1.4.5", diff --git a/src/user-management/user-management.ts b/src/user-management/user-management.ts index 2639246e0..d2e7c0670 100644 --- a/src/user-management/user-management.ts +++ b/src/user-management/user-management.ts @@ -1,4 +1,5 @@ import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose'; +import qs from 'qs'; import { OauthException } from '../common/exceptions/oauth.exception'; import { IronSessionProvider } from '../common/iron-session/iron-session-provider'; import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize'; @@ -145,24 +146,13 @@ import { Session } from './session'; const toQueryString = ( options: Record, ): string => { - const searchParams = new URLSearchParams(); - const keys = Object.keys(options).sort(); - - for (const key of keys) { - const value = options[key]; - - if (Array.isArray(value)) { - value.forEach((item) => { - searchParams.append(key, item); - }); - } - - if (typeof value === 'string') { - searchParams.append(key, value); - } - } - - return searchParams.toString(); + return qs.stringify(options, { + arrayFormat: 'repeat', + // sorts the keys alphabetically to maintain backwards compatibility + sort: (a, b) => a.localeCompare(b), + // encodes space as + instead of %20 to maintain backwards compatibility + format: 'RFC1738', + }); }; export class UserManagement { From 11f31db1c8dfaa274e386e347576d447fe6621e8 Mon Sep 17 00:00:00 2001 From: Stacy Curry Date: Tue, 15 Jul 2025 18:52:41 -0500 Subject: [PATCH 2/2] add supoort for provider query params --- .../__snapshots__/user-management.spec.ts.snap | 2 ++ .../authorization-url-options.interface.ts | 1 + src/user-management/user-management.spec.ts | 18 ++++++++++++++++++ src/user-management/user-management.ts | 7 ++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/user-management/__snapshots__/user-management.spec.ts.snap b/src/user-management/__snapshots__/user-management.spec.ts.snap index c78be1306..e7be93c21 100644 --- a/src/user-management/__snapshots__/user-management.spec.ts.snap +++ b/src/user-management/__snapshots__/user-management.spec.ts.snap @@ -12,6 +12,8 @@ exports[`UserManagement getAuthorizationUrl with a provider generates an authori exports[`UserManagement getAuthorizationUrl with a provider with providerScopes generates an authorize url that includes the specified scopes 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=GoogleOAuth&provider_scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar&provider_scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadmin.directory.group&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; +exports[`UserManagement getAuthorizationUrl with a provider with providerScopes with providerQueryParams generates an authorize url that includes the specified query params 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=GoogleOAuth&provider_query_params%5Bbaz%5D=123&provider_query_params%5Bbool%5D=true&provider_query_params%5Bfoo%5D=bar&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; + exports[`UserManagement getAuthorizationUrl with a screenHint generates an authorize url with a screenHint 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=authkit&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code&screen_hint=sign-up"`; exports[`UserManagement getAuthorizationUrl with an organizationId generates an authorization URL with the organization 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&organization_id=organization_123&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; diff --git a/src/user-management/interfaces/authorization-url-options.interface.ts b/src/user-management/interfaces/authorization-url-options.interface.ts index 1c9ed27e1..5cec62ee7 100644 --- a/src/user-management/interfaces/authorization-url-options.interface.ts +++ b/src/user-management/interfaces/authorization-url-options.interface.ts @@ -12,6 +12,7 @@ export interface UserManagementAuthorizationURLOptions { domainHint?: string; loginHint?: string; provider?: string; + providerQueryParams?: Record; providerScopes?: string[]; prompt?: string; redirectUri: string; diff --git a/src/user-management/user-management.spec.ts b/src/user-management/user-management.spec.ts index c49549b12..ee7ba51b3 100644 --- a/src/user-management/user-management.spec.ts +++ b/src/user-management/user-management.spec.ts @@ -2021,6 +2021,24 @@ describe('UserManagement', () => { expect(url).toMatchSnapshot(); }); + + describe('with providerQueryParams', () => { + it('generates an authorize url that includes the specified query params', () => { + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + const url = workos.userManagement.getAuthorizationUrl({ + provider: 'GoogleOAuth', + clientId: 'proj_123', + redirectUri: 'example.com/auth/workos/callback', + providerQueryParams: { + foo: 'bar', + baz: 123, + bool: true, + }, + }); + expect(url).toMatchSnapshot(); + }); + }); }); }); diff --git a/src/user-management/user-management.ts b/src/user-management/user-management.ts index d2e7c0670..d70b7e24a 100644 --- a/src/user-management/user-management.ts +++ b/src/user-management/user-management.ts @@ -144,7 +144,10 @@ import { serializeUpdateOrganizationMembershipOptions } from './serializers/upda import { Session } from './session'; const toQueryString = ( - options: Record, + options: Record< + string, + string | string[] | Record | undefined + >, ): string => { return qs.stringify(options, { arrayFormat: 'repeat', @@ -1013,6 +1016,7 @@ export class UserManagement { loginHint, organizationId, provider, + providerQueryParams, providerScopes, prompt, redirectUri, @@ -1047,6 +1051,7 @@ export class UserManagement { domain_hint: domainHint, login_hint: loginHint, provider, + provider_query_params: providerQueryParams, provider_scopes: providerScopes, prompt, client_id: clientId,