Skip to content

Commit 5d7b78d

Browse files
committed
fix: safari itp handshake loo[
1 parent 02798f5 commit 5d7b78d

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,53 @@ describe('tokens.authenticateRequest(options)', () => {
877877
expect(requestState.toAuth()).toBeNull();
878878
});
879879

880+
test('cookieToken: returns signedIn when no clientUat but in redirect loop with valid session (Safari ITP workaround)', async () => {
881+
server.use(
882+
http.get('https://api.clerk.test/v1/jwks', () => {
883+
return HttpResponse.json(mockJwks);
884+
}),
885+
);
886+
887+
// Simulate Safari ITP scenario: valid session token, no client_uat, redirect loop detected
888+
const requestState = await authenticateRequest(
889+
mockRequestWithCookies(
890+
{},
891+
{
892+
__clerk_db_jwt: 'deadbeef',
893+
__session: mockJwt,
894+
__clerk_redirect_count: '1', // Redirect loop counter > 0
895+
},
896+
),
897+
mockOptions({
898+
secretKey: 'test_deadbeef',
899+
}),
900+
);
901+
902+
expect(requestState).toBeSignedIn();
903+
expect(requestState.toAuth()).toBeSignedInToAuth();
904+
});
905+
906+
test('cookieToken: returns handshake when no clientUat and redirect loop but invalid session token', async () => {
907+
// Simulate scenario where we're in a redirect loop but the session token is invalid
908+
const requestState = await authenticateRequest(
909+
mockRequestWithCookies(
910+
{},
911+
{
912+
__clerk_db_jwt: 'deadbeef',
913+
__session: mockMalformedJwt,
914+
__clerk_redirect_count: '1',
915+
},
916+
),
917+
mockOptions({
918+
secretKey: 'test_deadbeef',
919+
}),
920+
);
921+
922+
// Should still return handshake since token verification failed
923+
expect(requestState).toMatchHandshake({ reason: AuthErrorReason.SessionTokenWithoutClientUAT });
924+
expect(requestState.toAuth()).toBeNull();
925+
});
926+
880927
test('cookieToken: returns handshake when no cookies in development [5y]', async () => {
881928
const requestState = await authenticateRequest(
882929
mockRequestWithCookies({}),

packages/backend/src/tokens/request.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,27 @@ export const authenticateRequest: AuthenticateRequest = (async (
538538
});
539539
}
540540

541-
// This can eagerly run handshake since client_uat is SameSite=Strict in dev
541+
// This can eagerly run handshake since client_uat is SameSite=Strict in dev.
542+
// However, Safari's ITP can block the client_uat cookie during cross-site redirects,
543+
// causing infinite redirect loops. If we detect a redirect loop and have a valid
544+
// session token, authenticate the user instead of triggering another handshake.
542545
if (!hasActiveClient && hasSessionToken) {
546+
if (authenticateContext.handshakeRedirectLoopCounter > 0) {
547+
try {
548+
const { data } = await verifyToken(authenticateContext.sessionTokenInCookie!, authenticateContext);
549+
if (data) {
550+
return signedIn({
551+
tokenType: TokenType.SessionToken,
552+
authenticateContext,
553+
sessionClaims: data,
554+
headers: new Headers(),
555+
token: authenticateContext.sessionTokenInCookie!,
556+
});
557+
}
558+
} catch {
559+
// Token verification failed, proceed with normal handshake flow
560+
}
561+
}
543562
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SessionTokenWithoutClientUAT, '');
544563
}
545564

0 commit comments

Comments
 (0)