Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/polite-satellites-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': patch
---

Improve satellite-domain redirect loop diagnostics.
34 changes: 34 additions & 0 deletions packages/backend/src/tokens/__tests__/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,40 @@ describe('tokens.authenticateRequest(options)', () => {
});
});

test('cookieToken: logs satellite-domain guidance when satellite sync enters a redirect loop', async () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

const requestState = await authenticateRequest(
mockRequestWithCookies(
{ ...defaultHeaders, 'sec-fetch-dest': 'document' },
{
__client_uat: '0',
__clerk_redirect_count: '3',
},
`http://satellite.example/path?__clerk_synced=false`,
),
mockOptions({
secretKey: 'deadbeef',
publishableKey: PK_LIVE,
signInUrl: 'https://primary.example/sign-in',
isSatellite: true,
domain: 'satellite.example',
}),
);

expect(requestState).toBeSignedOut({
reason: AuthErrorReason.SatelliteCookieNeedsSyncing,
isSatellite: true,
domain: 'satellite.example',
signInUrl: 'https://primary.example/sign-in',
});
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Satellite-domain authentication'));
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('configured primary or satellite domain'));
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('preview deployments'));

consoleSpy.mockRestore();
});

test('cookieToken: triggers handshake when satelliteAutoSync is not set but __clerk_synced=false is present - dev', async () => {
const requestState = await authenticateRequest(
mockRequestWithCookies(
Expand Down
9 changes: 8 additions & 1 deletion packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ export const authenticateRequest: AuthenticateRequest = (async (
// proceed with triggering handshake.
const isRedirectLoop = handshakeService.checkAndTrackRedirectLoop(handshakeHeaders);
if (isRedirectLoop) {
const msg = `Clerk: Refreshing the session token resulted in an infinite redirect loop. This usually means that your Clerk instance keys do not match - make sure to copy the correct publishable and secret keys from the Clerk dashboard.`;
const msg = getHandshakeRedirectLoopMessage(reason);
console.log(msg);
return signedOut({
tokenType: TokenType.SessionToken,
Expand All @@ -351,6 +351,13 @@ export const authenticateRequest: AuthenticateRequest = (async (
return handshake(authenticateContext, reason, message, handshakeHeaders);
}

function getHandshakeRedirectLoopMessage(reason: string): string {
if (reason === AuthErrorReason.SatelliteCookieNeedsSyncing) {
return `Clerk: Satellite-domain authentication resulted in an infinite redirect loop. Check that this request is using a configured primary or satellite domain for the production instance. For preview deployments, use a development/staging Clerk instance or a supported configured preview-domain setup.`;
}
return `Clerk: Refreshing the session token resulted in an infinite redirect loop. This usually means that your Clerk instance keys do not match - make sure to copy the correct publishable and secret keys from the Clerk dashboard.`;
}

/**
* Determines if a handshake must occur to resolve a mismatch between the organization as specified
* by the URL (according to the options) and the actual active organization on the session.
Expand Down
Loading