Skip to content

Commit bcbb2c9

Browse files
authored
fix(clerk-js): Respect props passed to buildSignInUrl and buildSignUpUrl (#3361)
* fix(clerk-react): Correctly map redirect prop names * fix(clerk-js): Respect props passed to buildSignInUrl and buildSignUpUrl across navigations
1 parent 6f61130 commit bcbb2c9

14 files changed

+215
-213
lines changed

.changeset/wet-peaches-grow.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/clerk-react': patch
4+
---
5+
6+
Respect the `signInForceRedirectUrl`, `signInFallbackRedirectUrl`, `signUpForceRedirectUrl` and `signUpFallbackRedirectUrl` props passed to `SignInButton`, `SignUpButton` and the low-level `window.Clerk.buildSignInUrl` & `window.Clerk.buildSignUpUrl` methods. These props allow you to control the redirect behavior of the `SignIn` and `SignUp` components. For more information, refer to the [Custom Redirects](https://clerk.com/docs/guides/custom-redirects) guide.

packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -123,33 +123,33 @@ describe('Clerk singleton - Redirects', () => {
123123
});
124124

125125
it('redirects to signInUrl for development instance', async () => {
126-
await clerkForDevelopmentInstance.redirectToSignIn({ redirectUrl: 'https://www.example.com/' });
126+
await clerkForDevelopmentInstance.redirectToSignIn({ redirectUrl: '/example' });
127127
expect(mockNavigate).toHaveBeenCalledWith(
128-
'/sign-in#/?redirect_url=https%3A%2F%2Fwww.example.com%2F',
128+
'/sign-in#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample',
129129
undefined,
130130
);
131131
});
132132

133133
it('redirects to signInUrl for production instance', async () => {
134-
await clerkForProductionInstance.redirectToSignIn({ redirectUrl: 'https://www.example.com/' });
134+
await clerkForProductionInstance.redirectToSignIn({ redirectUrl: '/example' });
135135
expect(mockNavigate).toHaveBeenCalledWith(
136-
'/sign-in#/?redirect_url=https%3A%2F%2Fwww.example.com%2F',
136+
'/sign-in#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample',
137137
undefined,
138138
);
139139
});
140140

141141
it('redirects to signUpUrl for development instance', async () => {
142-
await clerkForDevelopmentInstance.redirectToSignUp({ redirectUrl: 'https://www.example.com/' });
142+
await clerkForDevelopmentInstance.redirectToSignUp({ redirectUrl: '/example' });
143143
expect(mockNavigate).toHaveBeenCalledWith(
144-
'/sign-up#/?redirect_url=https%3A%2F%2Fwww.example.com%2F',
144+
'/sign-up#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample',
145145
undefined,
146146
);
147147
});
148148

149149
it('redirects to signUpUrl for production instance', async () => {
150-
await clerkForProductionInstance.redirectToSignUp({ redirectUrl: 'https://www.example.com/' });
150+
await clerkForProductionInstance.redirectToSignUp({ redirectUrl: '/example' });
151151
expect(mockNavigate).toHaveBeenCalledWith(
152-
'/sign-up#/?redirect_url=https%3A%2F%2Fwww.example.com%2F',
152+
'/sign-up#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample',
153153
undefined,
154154
);
155155
});
@@ -220,31 +220,31 @@ describe('Clerk singleton - Redirects', () => {
220220
const host = 'http://another-test.host';
221221

222222
it('redirects to signInUrl for development instance', async () => {
223-
await clerkForDevelopmentInstance.redirectToSignIn({ redirectUrl: 'https://www.example.com/' });
223+
await clerkForDevelopmentInstance.redirectToSignIn({ redirectUrl: '/example' });
224224
expect(mockHref).toHaveBeenCalledTimes(1);
225225
expect(mockHref).toHaveBeenCalledWith(
226-
`${host}/sign-in?__clerk_db_jwt=deadbeef#/?redirect_url=https%3A%2F%2Fwww.example.com%2F`,
226+
`${host}/sign-in?__clerk_db_jwt=deadbeef#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample`,
227227
);
228228
});
229229

230230
it('redirects to signInUrl for production instance', async () => {
231-
await clerkForProductionInstance.redirectToSignIn({ redirectUrl: 'https://www.example.com/' });
231+
await clerkForProductionInstance.redirectToSignIn({ redirectUrl: '/example' });
232232
expect(mockHref).toHaveBeenCalledTimes(1);
233-
expect(mockHref).toHaveBeenCalledWith(`${host}/sign-in#/?redirect_url=https%3A%2F%2Fwww.example.com%2F`);
233+
expect(mockHref).toHaveBeenCalledWith(`${host}/sign-in#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample`);
234234
});
235235

236236
it('redirects to signUpUrl for development instance', async () => {
237-
await clerkForDevelopmentInstance.redirectToSignUp({ redirectUrl: 'https://www.example.com/' });
237+
await clerkForDevelopmentInstance.redirectToSignUp({ redirectUrl: '/example' });
238238
expect(mockHref).toHaveBeenCalledTimes(1);
239239
expect(mockHref).toHaveBeenCalledWith(
240-
`${host}/sign-up?__clerk_db_jwt=deadbeef#/?redirect_url=https%3A%2F%2Fwww.example.com%2F`,
240+
`${host}/sign-up?__clerk_db_jwt=deadbeef#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample`,
241241
);
242242
});
243243

244244
it('redirects to signUpUrl for production instance', async () => {
245-
await clerkForProductionInstance.redirectToSignUp({ redirectUrl: 'https://www.example.com/' });
245+
await clerkForProductionInstance.redirectToSignUp({ redirectUrl: '/example' });
246246
expect(mockHref).toHaveBeenCalledTimes(1);
247-
expect(mockHref).toHaveBeenCalledWith(`${host}/sign-up#/?redirect_url=https%3A%2F%2Fwww.example.com%2F`);
247+
expect(mockHref).toHaveBeenCalledWith(`${host}/sign-up#/?redirect_url=http%3A%2F%2Ftest.host%2Fexample`);
248248
});
249249

250250
it('redirects to userProfileUrl', async () => {

packages/clerk-js/src/core/clerk.ts

+20-12
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type {
3737
OrganizationProfileProps,
3838
OrganizationResource,
3939
OrganizationSwitcherProps,
40+
RedirectOptions,
4041
Resources,
4142
SDKMetadata,
4243
SetActiveParams,
@@ -826,19 +827,20 @@ export class Clerk implements ClerkInterface {
826827

827828
return setDevBrowserJWTInURL(toURL, devBrowserJwt).href;
828829
}
829-
830830
public buildSignInUrl(options?: SignInRedirectOptions): string {
831-
return this.#buildUrl('signInUrl', {
832-
...options?.initialValues,
833-
redirect_url: options?.redirectUrl || window.location.href,
834-
});
831+
return this.#buildUrl(
832+
'signInUrl',
833+
{ ...options, redirectUrl: options?.redirectUrl || window.location.href },
834+
options?.initialValues,
835+
);
835836
}
836837

837838
public buildSignUpUrl(options?: SignUpRedirectOptions): string {
838-
return this.#buildUrl('signUpUrl', {
839-
...options?.initialValues,
840-
redirect_url: options?.redirectUrl || window.location.href,
841-
});
839+
return this.#buildUrl(
840+
'signUpUrl',
841+
{ ...options, redirectUrl: options?.redirectUrl || window.location.href },
842+
options?.initialValues,
843+
);
842844
}
843845

844846
public buildUserProfileUrl(): string {
@@ -1642,13 +1644,19 @@ export class Clerk implements ClerkInterface {
16421644
});
16431645
};
16441646

1645-
#buildUrl = (key: 'signInUrl' | 'signUpUrl', params?: Record<string, string>): string => {
1647+
#buildUrl = (
1648+
key: 'signInUrl' | 'signUpUrl',
1649+
options: RedirectOptions,
1650+
_initValues?: Record<string, string>,
1651+
): string => {
16461652
if (!key || !this.loaded || !this.#environment || !this.#environment.displayConfig) {
16471653
return '';
16481654
}
16491655
const signInOrUpUrl = this.#options[key] || this.#environment.displayConfig[key];
1650-
const redirectUrls = new RedirectUrls(this.#options, params);
1651-
return this.buildUrlWithAuth(redirectUrls.appendPreservedPropsToUrl(signInOrUpUrl, params));
1656+
const redirectUrls = new RedirectUrls(this.#options, options).toSearchParams();
1657+
const initValues = new URLSearchParams(_initValues || {});
1658+
const url = buildURL({ base: signInOrUpUrl, hashSearchParams: [initValues, redirectUrls] }, { stringify: true });
1659+
return this.buildUrlWithAuth(url);
16521660
};
16531661

16541662
assertComponentsReady(controls: unknown): asserts controls is ReturnType<MountComponentRenderer> {

packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ export const useSignUpContext = (): SignUpContextType => {
9191
let signUpUrl = (ctx.routing === 'path' && ctx.path) || options.signUpUrl || displayConfig.signUpUrl;
9292
let signInUrl = ctx.signInUrl || options.signInUrl || displayConfig.signInUrl;
9393

94-
signUpUrl = redirectUrls.appendPreservedPropsToUrl(signUpUrl, queryParams);
95-
signInUrl = redirectUrls.appendPreservedPropsToUrl(signInUrl, queryParams);
94+
const preservedParams = redirectUrls.getPreservedSearchParams();
95+
signInUrl = buildURL({ base: signInUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
96+
signUpUrl = buildURL({ base: signUpUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
9697

9798
// TODO: Avoid building this url again to remove duplicate code. Get it from window.Clerk instead.
9899
const secondFactorUrl = buildURL({ base: signInUrl, hashPath: '/factor-two' }, { stringify: true });
@@ -161,8 +162,10 @@ export const useSignInContext = (): SignInContextType => {
161162
let signInUrl = (ctx.routing === 'path' && ctx.path) || options.signInUrl || displayConfig.signInUrl;
162163
let signUpUrl = ctx.signUpUrl || options.signUpUrl || displayConfig.signUpUrl;
163164

164-
signInUrl = redirectUrls.appendPreservedPropsToUrl(signInUrl, queryParams);
165-
signUpUrl = redirectUrls.appendPreservedPropsToUrl(signUpUrl, queryParams);
165+
const preservedParams = redirectUrls.getPreservedSearchParams();
166+
signInUrl = buildURL({ base: signInUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
167+
signUpUrl = buildURL({ base: signUpUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
168+
166169
const signUpContinueUrl = buildURL({ base: signUpUrl, hashPath: '/continue' }, { stringify: true });
167170

168171
return {

packages/clerk-js/src/ui/router/RouteContext.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ParsedQs } from 'qs';
21
import React from 'react';
32

43
export interface RouteContextValue {
@@ -15,7 +14,7 @@ export interface RouteContextValue {
1514
refresh: () => void;
1615
params: { [key: string]: string };
1716
queryString: string;
18-
queryParams: ParsedQs;
17+
queryParams: Record<string, string>;
1918
preservedParams?: string[];
2019
getMatchData: (path?: string, index?: boolean) => false | object;
2120
urlStateParam?: {

packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts

+14-47
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ describe('redirectUrls', () => {
247247
});
248248

249249
describe('search params', () => {
250-
it('appends only the preserved props', () => {
250+
it('flattens and returns all params', () => {
251251
const redirectUrls = new RedirectUrls(
252252
{
253253
signInFallbackRedirectUrl: 'sign-in-fallback-redirect-url',
@@ -263,66 +263,33 @@ describe('redirectUrls', () => {
263263
);
264264

265265
const params = redirectUrls.toSearchParams();
266-
expect([...params.keys()].length).toBe(1);
266+
expect([...params.keys()].length).toBe(3);
267+
expect(params.get('sign_in_force_redirect_url')).toBe(
268+
`${mockWindowLocation.href}props-sign-in-force-redirect-url`,
269+
);
270+
expect(params.get('sign_up_fallback_redirect_url')).toBe(
271+
`${mockWindowLocation.href}search-param-sign-up-fallback-redirect-url`,
272+
);
267273
expect(params.get('redirect_url')).toBe(`${mockWindowLocation.href}search-param-redirect-url`);
268274
});
269275
});
270276

271-
describe('append to url', () => {
272-
it('does not append redirect urls from options to the url if the url is same origin', () => {
277+
describe('preserved search params', () => {
278+
it('does not return redirect urls if they are not in the preserved props array', () => {
273279
const redirectUrls = new RedirectUrls({
274280
signInFallbackRedirectUrl: 'sign-in-fallback-redirect-url',
275281
signUpFallbackRedirectUrl: 'sign-up-fallback-redirect-url',
276282
});
277283

278-
const url = redirectUrls.appendPreservedPropsToUrl('https://www.clerk.com');
279-
expect(url).toBe('https://www.clerk.com/');
280-
});
281-
282-
it('appends redirect urls from options to the url if the url is cross origin', () => {
283-
const redirectUrls = new RedirectUrls({}, {}, { redirect_url: '/search-param-redirect-url' });
284-
285-
const url = redirectUrls.appendPreservedPropsToUrl('https://www.example.com');
286-
expect(url).toContain('search-param-redirect-url');
287-
});
288-
289-
it('overrides the existing search params', () => {
290-
const redirectUrls = new RedirectUrls(
291-
{
292-
signInFallbackRedirectUrl: 'sign-in-fallback-redirect-url',
293-
signUpFallbackRedirectUrl: 'sign-up-fallback-redirect-url',
294-
},
295-
{},
296-
{ redirect_url: '/search-param-redirect-url' },
297-
);
298-
299-
const url = redirectUrls.appendPreservedPropsToUrl('https://www.example.com?redirect_url=existing');
300-
expect(url).toBe(
301-
'https://www.example.com/?redirect_url=existing#/?redirect_url=https%3A%2F%2Fwww.clerk.com%2Fsearch-param-redirect-url',
302-
);
284+
const params = redirectUrls.getPreservedSearchParams();
285+
expect([...params.keys()].length).toBe(0);
303286
});
304287

305288
it('appends redirect urls from props to the url even if the url is same origin', () => {
306289
const redirectUrls = new RedirectUrls({}, {}, { redirect_url: '/search-param-redirect-url' });
307290

308-
const url = redirectUrls.appendPreservedPropsToUrl('https://www.clerk.com');
309-
expect(url).toContain('search-param-redirect-url');
310-
});
311-
312-
it('does not append redirect urls from props to the url if the url is same origin if they match the options urls', () => {
313-
const redirectUrls = new RedirectUrls(
314-
{
315-
signInFallbackRedirectUrl: 'sign-in-fallback-redirect-url',
316-
signUpFallbackRedirectUrl: 'sign-up-fallback-redirect-url',
317-
},
318-
{
319-
signInFallbackRedirectUrl: 'sign-in-fallback-redirect-url',
320-
signUpFallbackRedirectUrl: 'sign-up-fallback-redirect-url',
321-
},
322-
);
323-
324-
const url = redirectUrls.appendPreservedPropsToUrl('https://www.clerk.com');
325-
expect(url).toBe('https://www.clerk.com/');
291+
const params = redirectUrls.getPreservedSearchParams();
292+
expect(params.get('redirect_url')).toContain('search-param-redirect-url');
326293
});
327294
});
328295
});

0 commit comments

Comments
 (0)