@@ -173,9 +173,17 @@ ${error.getFullMessage()}`,
173173 if ( isRequestEligibleForHandshake ( authenticateContext ) ) {
174174 // Right now the only usage of passing in different headers is for multi-domain sync, which redirects somewhere else.
175175 // In the future if we want to decorate the handshake redirect with additional headers per call we need to tweak this logic.
176- return handshake ( authenticateContext , reason , message , headers ?? buildRedirectToHandshake ( ) ) ;
176+ const handshakeHeaders = headers ?? buildRedirectToHandshake ( ) ;
177+ // Introduce the mechanism to protect for infinite handshake redirect loops
178+ // using a cookie and returning true if it's infinite redirect loop or false if we can
179+ // proceed with triggering handshake.
180+ const isRedirectLoop = setHandshakeInfiniteRedirectionLoopHeaders ( handshakeHeaders ) ;
181+ if ( isRedirectLoop ) {
182+ return signedOut ( authenticateContext , reason , message ) ;
183+ }
184+ return handshake ( authenticateContext , reason , message , handshakeHeaders ) ;
177185 }
178- return signedOut ( authenticateContext , reason , message , new Headers ( ) ) ;
186+ return signedOut ( authenticateContext , reason , message ) ;
179187 }
180188
181189 async function authenticateRequestWithTokenInHeader ( ) {
@@ -193,6 +201,20 @@ ${error.getFullMessage()}`,
193201 }
194202 }
195203
204+ // We want to prevent infinite handshake redirection loops.
205+ // We incrementally set a `__clerk_redirection_loop` cookie, and when it loops 3 times, we throw an error.
206+ // We also utilize the `referer` header to skip the prefetch requests.
207+ function setHandshakeInfiniteRedirectionLoopHeaders ( headers : Headers ) : boolean {
208+ if ( authenticateContext . handshakeRedirectLoopCounter === 3 ) {
209+ return true ;
210+ }
211+
212+ const newCounterValue = authenticateContext . handshakeRedirectLoopCounter + 1 ;
213+ const cookieName = constants . Cookies . InfiniteRedirectionLoopCookie ;
214+ headers . append ( 'Set-Cookie' , `${ cookieName } =${ newCounterValue } ; SameSite=Lax; HttpOnly; Max-Age=3` ) ;
215+ return false ;
216+ }
217+
196218 function handleHandshakeTokenVerificationErrorInDevelopment ( error : TokenVerificationError ) {
197219 // In development, the handshake token is being transferred in the URL as a query parameter, so there is no
198220 // possibility of collision with a handshake token of another app running on the same local domain
@@ -223,12 +245,11 @@ ${error.getFullMessage()}`,
223245 error . reason === TokenVerificationErrorReason . JWKKidMismatch ||
224246 error . reason === TokenVerificationErrorReason . JWKFailedToResolve
225247 ) {
226- // Let the request go through and eventually retry another handshake
227- // TODO: set a cookie so break the infinite loop
248+ // Let the request go through and eventually retry another handshake,
249+ // only if needed - a handshake will be thrown if another rule matches
228250 return ;
229251 }
230- // TODO: if N retries reached, return signedOut
231- const msg = `Clerk: Handshake token verification failed with "${ error . reason } "` ;
252+ const msg = `Clerk: Handshake token verification failed with "${ error . getFullMessage ( ) } "` ;
232253 return signedOut ( authenticateContext , AuthErrorReason . UnexpectedError , msg ) ;
233254 }
234255
@@ -249,10 +270,15 @@ ${error.getFullMessage()}`,
249270 try {
250271 return await resolveHandshake ( ) ;
251272 } catch ( error ) {
252- if ( error instanceof TokenVerificationError ) {
253- authenticateContext . instanceType === 'development'
254- ? handleHandshakeTokenVerificationErrorInDevelopment ( error )
255- : handleHandshakeTokenVerificationErrorInProduction ( error ) ;
273+ if ( error instanceof TokenVerificationError && authenticateContext . instanceType === 'development' ) {
274+ handleHandshakeTokenVerificationErrorInDevelopment ( error ) ;
275+ }
276+
277+ if ( error instanceof TokenVerificationError && authenticateContext . instanceType === 'production' ) {
278+ const terminateEarly = handleHandshakeTokenVerificationErrorInProduction ( error ) ;
279+ if ( terminateEarly ) {
280+ return terminateEarly ;
281+ }
256282 }
257283 }
258284 }
0 commit comments