Skip to content

chore(clerk-js): Extend buildRedirectToHandshake to accept search params #6277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
6 changes: 6 additions & 0 deletions .changeset/wet-dryers-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clerk/backend": patch
---

Extend `buildRedirectToHandshake` to accept search params
and track delta between `session.iat` and `client.uat` in case `iat < uat`
8 changes: 8 additions & 0 deletions packages/backend/src/tokens/__tests__/handshake.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ describe('HandshakeService', () => {
);
});

it('should include additional search params when provided', () => {
const headers = handshakeService.buildRedirectToHandshake('test-reason', { iat_uat_delta: '100' });
const location = headers.get(constants.Headers.Location) ?? '';
const url = new URL(location);

expect(url.searchParams.get('iat_uat_delta')).toBe('100');
});

it('should use proxy URL when available', () => {
mockAuthenticateContext.proxyUrl = 'https://my-proxy.example.com';
// Simulate what parsePublishableKey does when proxy URL is provided
Expand Down
11 changes: 10 additions & 1 deletion packages/backend/src/tokens/handshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ export class HandshakeService {
/**
* Builds the redirect headers for a handshake request
* @param reason - The reason for the handshake (e.g. 'session-token-expired')
* @param additionalSearchParams - Additional search params to append to the handshake URL (e.g. to help with observability)
* @returns Headers object containing the Location header for redirect
* @throws Error if clerkUrl is missing in authenticateContext
*/
buildRedirectToHandshake(reason: string): Headers {
buildRedirectToHandshake(reason: string, additionalSearchParams?: Record<string, string | undefined>): Headers {
if (!this.authenticateContext?.clerkUrl) {
throw new Error('Missing clerkUrl in authenticateContext');
}
Expand Down Expand Up @@ -163,6 +164,14 @@ export class HandshakeService {
});
}

if (additionalSearchParams) {
Object.entries(additionalSearchParams).forEach(([key, value]) => {
if (typeof value !== 'undefined') {
url.searchParams.append(key, value);
}
});
}

return new Headers({ [constants.Headers.Location]: url.href });
}

Expand Down
13 changes: 11 additions & 2 deletions packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export const authenticateRequest: AuthenticateRequest = (async (
reason: string,
message: string,
headers?: Headers,
handshakeSearchParams?: Record<string, string | undefined>,
): SignedInState | SignedOutState | HandshakeState {
if (!handshakeService.isRequestEligibleForHandshake()) {
return signedOut({
Expand All @@ -306,7 +307,7 @@ export const authenticateRequest: AuthenticateRequest = (async (

// Right now the only usage of passing in different headers is for multi-domain sync, which redirects somewhere else.
// In the future if we want to decorate the handshake redirect with additional headers per call we need to tweak this logic.
const handshakeHeaders = headers ?? handshakeService.buildRedirectToHandshake(reason);
const handshakeHeaders = headers ?? handshakeService.buildRedirectToHandshake(reason, handshakeSearchParams);

// Chrome aggressively caches inactive tabs. If we don't set the header here,
// all 307 redirects will be cached and the handshake will end up in an infinite loop.
Expand Down Expand Up @@ -534,7 +535,15 @@ export const authenticateRequest: AuthenticateRequest = (async (
}

if (decodeResult.payload.iat < authenticateContext.clientUat) {
return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SessionTokenIATBeforeClientUAT, '');
// Track delta for observability
const delta = authenticateContext.clientUat - decodeResult.payload.iat;
return handleMaybeHandshakeStatus(
authenticateContext,
AuthErrorReason.SessionTokenIATBeforeClientUAT,
'',
undefined,
{ iat_uat_delta: delta.toString() },
);
}

try {
Expand Down
Loading