From 648ef82c8762d4d011440b57e59bb3acde242600 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 10 Jan 2024 12:28:21 -0600 Subject: [PATCH 1/2] fix(backend): Fix unhandled handshake error --- .changeset/few-rings-push.md | 5 +++++ integration/tests/handshake.test.ts | 11 ++++++----- packages/backend/src/tokens/request.ts | 11 ++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 .changeset/few-rings-push.md 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..9e6908b66c0 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,12 @@ ${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. + } } /** From f8a1026f6baeff2b4d3cd78d6fc341a78b224bdf Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 10 Jan 2024 12:58:53 -0600 Subject: [PATCH 2/2] fix(backend): Handle development verification failure --- packages/backend/src/tokens/request.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 9e6908b66c0..d3d821434d7 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -209,6 +209,15 @@ ${error.getFullMessage()}`, } 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()}.`); + } } }