Skip to content

Commit 8ebbf1e

Browse files
authored
fix(backend): Complete satellite domain sync (#7018)
1 parent 64d3135 commit 8ebbf1e

File tree

3 files changed

+44
-11
lines changed

3 files changed

+44
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': patch
3+
---
4+
5+
Fix infinite redirect loop in multi-domain development flows by reordering authentication checks to prioritize satellite sync requests over dev-browser-sync handshakes.

packages/backend/src/tokens/__tests__/request.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,32 @@ describe('tokens.authenticateRequest(options)', () => {
810810
expect(requestState.toAuth()).toBeNull();
811811
});
812812

813+
test('cookieToken: primary responds to syncing takes precedence over dev-browser-sync in multi-domain flow', async () => {
814+
const sp = new URLSearchParams();
815+
sp.set('__clerk_redirect_url', 'http://localhost:3001/dashboard');
816+
sp.set('__clerk_db_jwt', mockJwt);
817+
const requestUrl = `http://localhost:3000/sign-in?${sp.toString()}`;
818+
const requestState = await authenticateRequest(
819+
mockRequestWithCookies(
820+
{ ...defaultHeaders, 'sec-fetch-dest': 'document' },
821+
{ __client_uat: '12345', __session: mockJwt, __clerk_db_jwt: mockJwt },
822+
requestUrl,
823+
),
824+
mockOptions({ secretKey: 'sk_test_deadbeef', isSatellite: false }),
825+
);
826+
827+
expect(requestState).toMatchHandshake({
828+
reason: AuthErrorReason.PrimaryRespondsToSyncing,
829+
});
830+
expect(requestState.message).toBe('');
831+
expect(requestState.toAuth()).toBeNull();
832+
833+
const location = requestState.headers.get('location');
834+
expect(location).toBeTruthy();
835+
expect(location).toContain('localhost:3001/dashboard');
836+
expect(location).toContain('__clerk_synced=true');
837+
});
838+
813839
test('cookieToken: returns signed out when no cookieToken and no clientUat in production [4y]', async () => {
814840
const requestState = await authenticateRequest(
815841
mockRequestWithCookies(),

packages/backend/src/tokens/request.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -461,16 +461,6 @@ export const authenticateRequest: AuthenticateRequest = (async (
461461
}
462462
}
463463
}
464-
/**
465-
* Otherwise, check for "known unknown" auth states that we can resolve with a handshake.
466-
*/
467-
if (
468-
authenticateContext.instanceType === 'development' &&
469-
authenticateContext.clerkUrl.searchParams.has(constants.QueryParameters.DevBrowser)
470-
) {
471-
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.DevBrowserSync, '');
472-
}
473-
474464
const isRequestEligibleForMultiDomainSync =
475465
authenticateContext.isSatellite && authenticateContext.secFetchDest === 'document';
476466

@@ -500,7 +490,9 @@ export const authenticateRequest: AuthenticateRequest = (async (
500490
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SatelliteCookieNeedsSyncing, '', headers);
501491
}
502492

503-
// Multi-domain development sync flow
493+
// Multi-domain development sync flow - primary responds to syncing
494+
// IMPORTANT: This must come BEFORE dev-browser-sync check to avoid the root domain
495+
// triggering its own handshakes when it's in the middle of handling a satellite sync request
504496
const redirectUrl = new URL(authenticateContext.clerkUrl).searchParams.get(
505497
constants.QueryParameters.ClerkRedirectUrl,
506498
);
@@ -524,6 +516,16 @@ export const authenticateRequest: AuthenticateRequest = (async (
524516
* End multi-domain sync flows
525517
*/
526518

519+
/**
520+
* Otherwise, check for "known unknown" auth states that we can resolve with a handshake.
521+
*/
522+
if (
523+
authenticateContext.instanceType === 'development' &&
524+
authenticateContext.clerkUrl.searchParams.has(constants.QueryParameters.DevBrowser)
525+
) {
526+
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.DevBrowserSync, '');
527+
}
528+
527529
if (authenticateContext.instanceType === 'development' && !hasDevBrowserToken) {
528530
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.DevBrowserMissing, '');
529531
}

0 commit comments

Comments
 (0)