diff --git a/.changeset/soft-snakes-hope.md b/.changeset/soft-snakes-hope.md new file mode 100644 index 00000000000..e327fd89665 --- /dev/null +++ b/.changeset/soft-snakes-hope.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Detect infinite interstitial redirect loop in middleware and throw error to inform the user. diff --git a/packages/nextjs/src/server/authMiddleware.ts b/packages/nextjs/src/server/authMiddleware.ts index ee840b86b6d..7bbefda0cca 100644 --- a/packages/nextjs/src/server/authMiddleware.ts +++ b/packages/nextjs/src/server/authMiddleware.ts @@ -34,6 +34,8 @@ type RouteMatcherWithNextTypedRoutes = | NextTypedRoute | (string & {}); +const INFINITE_REDIRECTION_LOOP_COOKIE = '__clerk_redirection_loop'; + /** * The default ideal matcher that excludes the _next directory (internals) and all static files, * but it will match the root route (/) and any routes that start with /api or /trpc. @@ -160,7 +162,8 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => { return handleUnknownState(requestState); } else if (requestState.isInterstitial) { logger.debug('authenticateRequest state is interstitial', requestState); - return handleInterstitialState(requestState, options); + const res = handleInterstitialState(requestState, options); + return assertInfiniteRedirectionLoop(req, res); } const auth = Object.assign(requestState.toAuth(), { @@ -287,3 +290,27 @@ const isRequestMethodIndicatingApiRoute = (req: NextRequest): boolean => { const requestMethod = req.method.toLowerCase(); return !['get', 'head', 'options'].includes(requestMethod); }; + +// When in development, we want to prevent infinite interstitial redirection loops. +// We incrementally set a `__clerk_redirection_loop` cookie, and when it loops 6 times, we throw an error. +// We also utilize the `referer` header to skip the prefetch requests. +const assertInfiniteRedirectionLoop = (req: NextRequest, res: NextResponse): NextResponse => { + if (!isDevelopmentFromApiKey(SECRET_KEY)) { + return res; + } + + const infiniteRedirectsCounter = Number(req.cookies.get(INFINITE_REDIRECTION_LOOP_COOKIE)?.value) || 0; + if (infiniteRedirectsCounter === 6) { + throw new Error('Oops! Looks like you ended up in an Infinite Redirection Loop.'); + } + + // Skip the prefetch requests (when hovering a Next Link element) + if (req.headers.get('referer') === req.url) { + res.cookies.set({ + name: INFINITE_REDIRECTION_LOOP_COOKIE, + value: `${infiniteRedirectsCounter + 1}`, + maxAge: 3, + }); + } + return res; +};