diff --git a/.changeset/few-rings-push.md b/.changeset/few-rings-push.md new file mode 100644 index 00000000000..1e0e56979fc --- /dev/null +++ b/.changeset/few-rings-push.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Fix an error in the handshake flow where the request would throw an unhandled error when verification of the handshake payload fails. diff --git a/integration/tests/handshake.test.ts b/integration/tests/handshake.test.ts index 23d1926dd8a..b0636403bc2 100644 --- a/integration/tests/handshake.test.ts +++ b/integration/tests/handshake.test.ts @@ -827,13 +827,14 @@ test.describe('Client handshake @generic', () => { }), redirect: 'manual', }); - expect(res.status).toBe(500); + expect(res.status).toBe(200); }); - test('Handshake result - prod - skew - clock ahead', async () => { + test('Handshake result - prod - session token expired and handshake stale', async () => { const config = generateConfig({ mode: 'live', }); + const { token: currentSessionToken, claims } = config.generateToken({ state: 'expired' }); const { token } = config.generateToken({ state: 'expired' }); const cookiesToSet = [`__session=${token};path=/`, 'foo=bar;path=/;domain=example.com']; const handshake = await config.generateHandshakeToken(cookiesToSet); @@ -841,12 +842,12 @@ test.describe('Client handshake @generic', () => { headers: new Headers({ 'X-Publishable-Key': config.pk, 'X-Secret-Key': config.sk, - Cookie: `__clerk_handshake=${handshake}`, + Cookie: `__clerk_handshake=${handshake};__session=${currentSessionToken};__client_uat=${claims.iat}`, 'Sec-Fetch-Dest': 'document', }), redirect: 'manual', }); - expect(res.status).toBe(500); + expect(res.status).toBe(307); }); test('Handshake result - prod - mismatched keys', async () => { @@ -866,6 +867,6 @@ test.describe('Client handshake @generic', () => { }), redirect: 'manual', }); - expect(res.status).toBe(500); + expect(res.status).toBe(200); }); }); diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index fb383605844..d3d821434d7 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -124,8 +124,8 @@ export async function authenticateRequest( if ( instanceType === 'development' && - (error.reason === TokenVerificationErrorReason.TokenExpired || - error.reason === TokenVerificationErrorReason.TokenNotActiveYet) + (error?.reason === TokenVerificationErrorReason.TokenExpired || + error?.reason === TokenVerificationErrorReason.TokenNotActiveYet) ) { error.tokenCarrier = 'cookie'; // This probably means we're dealing with clock skew @@ -204,7 +204,21 @@ ${error.getFullMessage()}`, * If we have a handshakeToken, resolve the handshake and attempt to return a definitive signed in or signed out state. */ if (authenticateContext.handshakeToken) { - return resolveHandshake(); + try { + return await resolveHandshake(); + } catch (error) { + // If for some reason the handshake token is invalid or stale, we ignore it and continue trying to authenticate the request. + // Worst case, the handshake will trigger again and return a refreshed token. + if (error instanceof TokenVerificationError && instanceType === 'development') { + if (error.reason === TokenVerificationErrorReason.TokenInvalidSignature) { + throw new Error( + `Clerk: Handshake token verification failed due to an invalid signature. If you have switched Clerk keys locally, clear your cookies and try again.`, + ); + } + + throw new Error(`Clerk: Handshake token verification failed: ${error.getFullMessage()}.`); + } + } } /**