Skip to content

fix(react-router): resolve getAuth per-request to prevent cross-user auth bleed#8896

Open
jacekradko wants to merge 9 commits into
mainfrom
jacek/rr-getauth-per-request
Open

fix(react-router): resolve getAuth per-request to prevent cross-user auth bleed#8896
jacekradko wants to merge 9 commits into
mainfrom
jacek/rr-getauth-per-request

Conversation

@jacekradko

@jacekradko jacekradko commented Jun 17, 2026

Copy link
Copy Markdown
Member

@clerk/react-router derived per-request auth from the React Router context: clerkMiddleware cached the resolved auth on args.context and getAuth / rootAuthLoader read it back. That is correct only while the context is unique per request. If any layer reuses the context across requests, a custom server, a shared getLoadContext, or a serverless adapter that reuses it on a warm instance, one request can be served another user's auth.

This hardens that path. The middleware still resolves auth once, but now caches it keyed by the Request object (which the runtime creates per request and an app cannot alias), and getAuth / rootAuthLoader read that, re-deriving from the request's own cookies only when the Request instance differs (React Router's action-to-loader hop). So a reused context can no longer return the wrong user, and token verification and refresh still run once per request. clerkMiddleware also logs a one-time warning when it detects a reused context.

Worth reviewing: resolveRequestState (the WeakMap<Request, ...> memo plus the re-derive fallback) and that the cache key is the Request, never the context.

Scope: this protects against a reused context. A layer that reuses the Request object itself (stale cookies and all) would be an adapter bug this can't paper over. This came out of a cross-user bleed investigation and is defensive hardening; it is not claimed to be the root-cause fix for any specific incident. Supersedes #8890.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed per-request auth resolution to prevent cross-user auth leakage when a shared React Router context is reused across concurrent requests.
    • Updated auth revalidation (including action → loader) so auth is re-derived from the current request.
  • Tests

    • Expanded concurrency, revalidation, and cache-miss coverage, including “no options bleed” and shared-context warning scenarios.
  • Versioning

    • Released a patch update for @clerk/react-router.

…auth bleed

getAuth and rootAuthLoader now re-derive the request's auth from args.request
instead of reading a value cached on the React Router context, so an app that
shares one RouterContextProvider across requests (a custom server or
getLoadContext that returns one instance) can no longer be served another user's
auth under concurrency, including across the action-to-loader hop. The middleware
stashes only identity-free options for the re-derivation to reuse, matching how
@clerk/nextjs resolves auth, with no node:async_hooks dependency. clerkMiddleware
also logs a warning once when it detects a context reused across requests.
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 17, 2026 8:29pm
swingset Ready Ready Preview, Comment Jun 17, 2026 8:29pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 23273d2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/react-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors React Router Clerk middleware to derive auth per-request from the live request object. Introduces middlewareConfigContext and exports resolveRequestState() to re-authenticate without relying on cached context values. getAuth and rootAuthLoader now call resolveRequestState(args) instead of reading from middleware context. The middleware detects context reuse across requests and logs a one-time warning. Comprehensive tests validate auth isolation under concurrent request interleaving and fresh request instances.

Changes

Request-scoped auth isolation

Layer / File(s) Summary
Core request-scoped re-derivation infrastructure
packages/react-router/src/server/clerkMiddleware.ts
Introduces middlewareConfigContext to hold static middleware configuration and exports resolveRequestState(), which re-authenticates from args.request by fetching stored config and calling clerkClient.authenticateRequest(). Caches RequestState in a WeakMap keyed by Request instance. Adds context-reuse detection using a shared probe context, logging via logger.warnOnce when the same RouterContextProvider is detected across different requests.
clerkMiddleware typed authentication and context wiring
packages/react-router/src/server/clerkMiddleware.ts
Builds a typed authenticateOptions object and passes it to clerkClient(...).authenticateRequest(...). After authentication, separates additionalState from a request-independent middlewareConfig, caches RequestState in the WeakMap, and updates middlewareConfigContext, authFnContext, and requestStateContext with both cached request state and additional middleware state.
getAuth and rootAuthLoader updated to use resolveRequestState
packages/react-router/src/server/getAuth.ts, packages/react-router/src/server/rootAuthLoader.ts
Both functions replace cached-context identity reads with a call to resolveRequestState(args). getAuth passes requestState.toAuth({ treatPendingAsSignedOut }) into getAuthObjectForAcceptedToken; rootAuthLoader passes the re-derived requestState into processRootAuthLoader.
Unit tests for getAuth and rootAuthLoader re-derivation
packages/react-router/src/server/__tests__/getAuth.test.ts, packages/react-router/src/server/__tests__/rootAuthLoader.test.ts
Updates tests to mock clerkClient via vi.mock() and configure authenticateRequest to return test RequestState objects. getAuth tests now supply middleware config via middlewareConfigContext and assert re-derivation. rootAuthLoader tests use a reusable makeContext() helper that stubs middleware config, request state, and auth function contexts.
Auth isolation concurrency and caching test harness
packages/react-router/src/server/__tests__/clerkMiddleware.authIsolation.test.ts
Adds comprehensive Vitest suite deterministically interleaving two concurrent requests through middleware. Verifies each loader correctly identifies its own userId from args.request under both shared and distinct RouterContextProvider scenarios. Tests authenticateRequest caching: single invocation per request when getAuth is called multiple times, and re-derivation on fresh Request instances. Includes "no options bleed" test validating request-scoped domain values are correctly passed to authenticateRequest.
Shared RouterContextProvider warning test suite
packages/react-router/src/server/__tests__/clerkMiddleware.sharedContextWarning.test.ts
Adds Vitest suite validating logger.warnOnce fires exactly once when two middleware invocations share the same RouterContextProvider instance across different requests, and never fires when each request receives a distinct provider. Mocks clerkClient, loadOptions, and spies on logger.warnOnce.
Release changeset entry
.changeset/rr-request-scoped-auth.md
Patch release metadata documenting the request-scoped auth fix. Records that getAuth() and rootAuthLoader() now derive current request auth from the request itself rather than a cached value on shared context, preventing cross-user auth leakage under concurrency. Notes that clerkMiddleware() logs a one-time warning when it detects a reused context across requests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 Hops between requests, no mix-up in sight,
Each query now knows whose token is right.
The context was shared—a sneaky old trick—
But resolveRequestState does the fix!
One warning, then silence, the rabbit runs free,
Per-request auth, as secure as can be. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and clearly describes the main fix: resolving getAuth per-request to prevent cross-user auth leakage in React Router when contexts are shared.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8896

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8896

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8896

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8896

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8896

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8896

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8896

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8896

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8896

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8896

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8896

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8896

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8896

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8896

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8896

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8896

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8896

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8896

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8896

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8896

commit: 23273d2

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-17T20:33:18.402Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 23273d2.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/react-router/src/server/__tests__/getAuth.test.ts (1)

16-21: ⚡ Quick win

Remove any from the mocked auth contract.

Using options?: any and as any weakens this test’s ability to catch type drift in toAuth/authenticateRequest wiring.

Suggested typed patch
+import type { ClerkClient } from '`@clerk/backend`';
 import { TokenType } from '`@clerk/backend/internal`';
 import type { LoaderFunctionArgs } from 'react-router';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
@@
     mockClerkClient.mockReturnValue({
       authenticateRequest: vi.fn().mockResolvedValue({
         headers: new Headers(),
-        toAuth: (options?: any) => ({ userId: 'user_xxx', tokenType: TokenType.SessionToken, ...options }),
+        toAuth: (options?: Record<string, unknown>) => ({
+          userId: 'user_xxx',
+          tokenType: TokenType.SessionToken,
+          ...(options ?? {}),
+        }),
       }),
-    } as any);
+    } as unknown as ClerkClient);
   });

As per coding guidelines, “Avoid any type - prefer unknown when type is uncertain” and “No any types without justification in code review.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/server/__tests__/getAuth.test.ts` around lines 16 -
21, In the mockClerkClient setup, remove the `any` types to improve type safety.
Replace the `options?: any` parameter in the toAuth function with a properly
typed parameter (likely based on the actual ClerkClient type or a subset
interface), and remove the `as any` type assertion by properly typing the entire
mock return value. Look at the actual ClerkClient interface from the Clerk
library to understand what types toAuth expects and what authenticateRequest
should return, then apply those types to the mock object instead of using the
loose `any` casting.

Source: Coding guidelines

packages/react-router/src/server/__tests__/rootAuthLoader.test.ts (1)

27-30: ⚡ Quick win

Avoid any casts in the new rootAuthLoader test path.

These two any casts make it easier for auth-state shape regressions to slip through this suite.

Suggested typed patch
+import type { ClerkClient } from '`@clerk/backend`';
 import { TokenType } from '`@clerk/backend/internal`';
@@
     mockClerkClient.mockReturnValue({
       authenticateRequest: vi.fn().mockResolvedValue(mockRequestState),
-    } as any);
+    } as unknown as ClerkClient);
   });
@@
+      type RedirectStateResult = {
+        clerkState: {
+          __internal_clerk_state: {
+            __signInForceRedirectUrl?: string;
+            __signUpForceRedirectUrl?: string;
+            __signInFallbackRedirectUrl?: string;
+            __signUpFallbackRedirectUrl?: string;
+          };
+        };
+      };
+
       const result = (await rootAuthLoader({
         context: makeContext({
           signInForceRedirectUrl: '/dashboard',
@@
         }),
         request: new Request('http://clerk.com'),
-      } as unknown as LoaderFunctionArgs)) as any;
+      } as unknown as LoaderFunctionArgs)) as RedirectStateResult;

As per coding guidelines, “Avoid any type - prefer unknown when type is uncertain” and “No any types without justification in code review.”

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/server/__tests__/rootAuthLoader.test.ts` around
lines 27 - 30, Replace the `as any` cast in the mockClerkClient.mockReturnValue
setup with proper typing that matches the actual shape of the Clerk client
object. Instead of casting to `any`, provide explicit type information for the
mock object structure that includes the authenticateRequest method and any other
required properties. Apply the same typing fix to the similar mock setup at the
other location in the file (around line 124) to ensure both mock instances have
proper type safety rather than using `any` casts.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/react-router/src/server/__tests__/getAuth.test.ts`:
- Around line 16-21: In the mockClerkClient setup, remove the `any` types to
improve type safety. Replace the `options?: any` parameter in the toAuth
function with a properly typed parameter (likely based on the actual ClerkClient
type or a subset interface), and remove the `as any` type assertion by properly
typing the entire mock return value. Look at the actual ClerkClient interface
from the Clerk library to understand what types toAuth expects and what
authenticateRequest should return, then apply those types to the mock object
instead of using the loose `any` casting.

In `@packages/react-router/src/server/__tests__/rootAuthLoader.test.ts`:
- Around line 27-30: Replace the `as any` cast in the
mockClerkClient.mockReturnValue setup with proper typing that matches the actual
shape of the Clerk client object. Instead of casting to `any`, provide explicit
type information for the mock object structure that includes the
authenticateRequest method and any other required properties. Apply the same
typing fix to the similar mock setup at the other location in the file (around
line 124) to ensure both mock instances have proper type safety rather than
using `any` casts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: eb8f63c6-035b-4c5d-8ccc-9f7bd9134392

📥 Commits

Reviewing files that changed from the base of the PR and between f4ecc13 and a978bb7.

📒 Files selected for processing (8)
  • .changeset/rr-request-scoped-auth.md
  • packages/react-router/src/server/__tests__/clerkMiddleware.authIsolation.test.ts
  • packages/react-router/src/server/__tests__/clerkMiddleware.sharedContextWarning.test.ts
  • packages/react-router/src/server/__tests__/getAuth.test.ts
  • packages/react-router/src/server/__tests__/rootAuthLoader.test.ts
  • packages/react-router/src/server/clerkMiddleware.ts
  • packages/react-router/src/server/getAuth.ts
  • packages/react-router/src/server/rootAuthLoader.ts

@jacekradko

jacekradko commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Perf note (updated for the per-request memo design)

Earlier numbers here assumed getAuth re-runs authenticateRequest on every call. That's no longer how it works: the middleware authenticates once and memoizes the result keyed by the Request; getAuth/rootAuthLoader read from that memo.

Re-measured locally (real RS256 networkless, JWKS warm):

path per getAuth call extra authenticateRequest
common case (loader on the Request the middleware saw) ~0.17µs (WeakMap hit + toAuth) 0
fresh-Request miss (action→loader revalidation) ~0.5ms one-time, then cached 1

The common path is effectively free and matches the previous cached behavior (both just call toAuth). The only added cost vs. before is one re-derive on a fresh Request instance, which is then cached, so repeat reads on it are hits again. That ~0.5ms is loadOptions rebuild ~0.35ms + RS256 verify ~0.19ms. Negligible for typical apps; the earlier "0.6-1.9ms per request" estimate assumed a re-derive per getAuth call, which no longer happens.

…memo

Resolve the request's auth once in clerkMiddleware and cache it keyed by the
Request object (not the shared context), so getAuth/rootAuthLoader reuse it and a
context reused across requests (custom server, shared getLoadContext, or a
serverless adapter on a warm instance) can't return another user. Re-derive from
the request's own cookies only on a Request-instance miss (action -> loader), so
token verification and refresh still happen once per request.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-router/src/server/__tests__/clerkMiddleware.authIsolation.test.ts`:
- Around line 162-170: The test in the middleware callback currently only calls
readUserId once on the loaderArgs, which means it cannot verify that the
re-derived auth state is being properly cached. To make the caching behavior
observable, call readUserId on the loaderArgs twice (keeping both assertions
that the result is 'user_A'), and keep the authSpy call count expectation at 2.
This ensures that if caching is not working, the spy would be called 3 or 4
times instead of 2, making the test fail and catch the bug.

In `@packages/react-router/src/server/clerkMiddleware.ts`:
- Around line 57-60: The return statement in the clerkMiddleware function that
calls authenticateRequest() does not cache the resulting RequestState, causing
repeated calls with the same request object to re-run authentication instead of
using a cached value. Capture the result of the authenticateRequest() call,
store it in the requestStateByRequest cache using the request as the key, and
then return the cached value to ensure subsequent calls for the same request
reuse the cached state instead of re-authenticating.
- Around line 171-172: The authFnContext and requestStateContext are being set
at lines 171-172 in clerkMiddleware.ts but are not part of the public API and
should be deprecated. Add `@internal` or `@deprecated` JSDoc markers to the export
declarations of authFnContext and requestStateContext at the top of
clerkMiddleware.ts to signal they are not supported for public use. Ensure the
internal implementation (including rootAuthLoader and other server code) uses
requestStateByRequest for the request-keyed cache instead of relying on these
context objects, so the contexts can be safely removed in a future major
version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7011b6fb-e040-4ddc-843b-b988789c9c42

📥 Commits

Reviewing files that changed from the base of the PR and between a978bb7 and 6a06393.

📒 Files selected for processing (5)
  • .changeset/rr-request-scoped-auth.md
  • packages/react-router/src/server/__tests__/clerkMiddleware.authIsolation.test.ts
  • packages/react-router/src/server/clerkMiddleware.ts
  • packages/react-router/src/server/getAuth.ts
  • packages/react-router/src/server/rootAuthLoader.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/rr-request-scoped-auth.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-router/src/server/rootAuthLoader.ts

Comment thread packages/react-router/src/server/clerkMiddleware.ts Outdated
Comment thread packages/react-router/src/server/clerkMiddleware.ts
…ache the result

The Request-miss fallback read the resolved auth options from the shared context
and never cached its result. Under a shared context an interleaved request could
overwrite those options, so a fresh action->loader Request could authenticate with
another request's domain/proxyUrl/isSatellite (or keyless options), and repeat
getAuth calls re-authenticated each time. Stash only request-independent config
(static options + resolved keys) and re-resolve the request-derived options from
args.request on the miss, then cache the result keyed by Request.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-router/src/server/clerkMiddleware.ts (1)

117-148: ⚠️ Potential issue | 🟠 Major

Add missing afterSignInUrl, afterSignUpUrl, and satelliteAutoSync to middleware configuration.

The middleware option type ClerkMiddlewareOptions does not expose afterSignInUrl, afterSignUpUrl, or satelliteAutoSync even though they are supported by the backend's AuthenticateRequestOptions and used in authentication logic. This means configured post-sign-in/sign-up redirect URLs and satellite auto-sync settings cannot be passed through the middleware.

Additionally, getPublicEnvVariables() already reads environment variables for afterSignInUrl and afterSignUpUrl (lines 31-32 in env.ts), but these are never integrated into loadOptions() or ClerkMiddlewareOptions. The satelliteAutoSync option is documented in the CHANGELOG as a clerkMiddleware configuration option but is currently unavailable.

Add these fields to ClerkMiddlewareOptions in types.ts, extract them in loadOptions(), and pass them through to authenticateOptions in clerkMiddleware.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/server/clerkMiddleware.ts` around lines 117 - 148,
Add support for the missing authentication options by first ensuring
`afterSignInUrl`, `afterSignUpUrl`, and `satelliteAutoSync` are included in the
`ClerkMiddlewareOptions` type definition in `types.ts`, then extracting these
fields in the `loadOptions()` function. In the `clerkMiddleware.ts` file, update
the destructuring block that pulls properties from `loadedOptions` to include
these three new fields alongside the existing ones, and then add these same
three properties to the `authenticateOptions` object being constructed so they
are passed through to the authentication request.
🧹 Nitpick comments (2)
packages/react-router/src/server/__tests__/getAuth.test.ts (1)

24-46: ⚡ Quick win

Assert the authenticateRequest call path explicitly in this regression test.

This test currently proves output shape, but it doesn’t lock in the critical behavior that getAuth re-derives via clerkClient(...).authenticateRequest(...). Please add call assertions so future refactors can’t silently bypass the request-based re-auth path.

Suggested test hardening
   const auth = await getAuth(args);

   expect(auth.userId).toBe('user_xxx');
   expect(auth.tokenType).toBe('session_token');
+  expect(mockClerkClient).toHaveBeenCalledWith(
+    args,
+    expect.objectContaining({
+      secretKey: 'sk_test_...',
+      publishableKey: 'pk_test_...',
+    }),
+  );
+  const returnedClient = mockClerkClient.mock.results[0]?.value as { authenticateRequest: ReturnType<typeof vi.fn> };
+  expect(returnedClient.authenticateRequest).toHaveBeenCalledTimes(1);

As per coding guidelines, **/*.{test,spec}.{ts,tsx} should implement comprehensive testing, including edge-case/regression coverage for new functionality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/server/__tests__/getAuth.test.ts` around lines 24 -
46, The test "should re-derive auth from the request when middleware ran"
currently only verifies the output shape (userId and tokenType) but doesn't
assert that the critical re-derivation path via
clerkClient().authenticateRequest() is actually being called. Add explicit call
assertions by mocking or spying on the clerkClient and its authenticateRequest
method, then verify with expect statements that authenticateRequest was invoked
with the expected request object. This ensures future refactors cannot
accidentally bypass the request-based re-authentication path.

Source: Coding guidelines

packages/react-router/src/server/clerkMiddleware.ts (1)

23-28: ⚡ Quick win

Mark the new exported plumbing internal or make it public-API ready.

middlewareConfigContext and resolveRequestState() look like internal plumbing but are exported; if they can surface in generated docs or direct imports, add @internal JSDoc, or document them fully and avoid exposing RequestState<any>. As per coding guidelines, public/reference-facing APIs in packages/**/src require accurate JSDoc, and generated Clerk Docs treat such JSDoc as customer-facing documentation.

📝 Internal-only option
-// Request-INDEPENDENT config (the static clerkMiddleware options plus resolved keys)
-// used to re-derive auth on a Request-instance miss. It deliberately does NOT hold
-// resolved request-derived values (domain/proxyUrl/isSatellite); those are
-// re-resolved per request from args.request, so a shared context can't leak one
-// request's resolved options to another.
+/**
+ * `@internal`
+ *
+ * Request-independent config used to re-derive auth on a Request-instance miss.
+ * It deliberately does not hold resolved request-derived values.
+ */
 export const middlewareConfigContext = createContext<ClerkMiddlewareOptions | null>(null);
@@
 /**
+ * `@internal`
+ *
  * Auth state for this request. Reuses what clerkMiddleware already resolved (keyed

Also applies to: 39-46

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/src/server/clerkMiddleware.ts` around lines 23 - 28,
The exported symbols middlewareConfigContext and resolveRequestState() appear to
be internal implementation details but lack proper JSDoc documentation. Since
these symbols are exported from packages/react-router/src and could surface in
generated documentation or direct imports, add `@internal` JSDoc comments above
both middlewareConfigContext (the createContext call) and the
resolveRequestState function definition to mark them as internal-only plumbing,
preventing them from appearing in public-facing Clerk documentation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-router/src/server/clerkMiddleware.ts`:
- Line 37: Concurrent calls to getAuth() and rootAuthLoader() for the same
Request object are duplicating authenticateRequest() work because they don't see
the in-flight promise. Modify the requestStateByRequest WeakMap to store the
authentication promise before awaiting it, allowing concurrent calls to the same
Request to reuse the existing promise instead of creating new ones. When the
promise resolves or rejects, clear it from the map so subsequent requests will
trigger a fresh authentication. This requires storing the promise itself (not
just the final RequestState) and checking for existing in-flight promises before
calling authenticateRequest().

---

Outside diff comments:
In `@packages/react-router/src/server/clerkMiddleware.ts`:
- Around line 117-148: Add support for the missing authentication options by
first ensuring `afterSignInUrl`, `afterSignUpUrl`, and `satelliteAutoSync` are
included in the `ClerkMiddlewareOptions` type definition in `types.ts`, then
extracting these fields in the `loadOptions()` function. In the
`clerkMiddleware.ts` file, update the destructuring block that pulls properties
from `loadedOptions` to include these three new fields alongside the existing
ones, and then add these same three properties to the `authenticateOptions`
object being constructed so they are passed through to the authentication
request.

---

Nitpick comments:
In `@packages/react-router/src/server/__tests__/getAuth.test.ts`:
- Around line 24-46: The test "should re-derive auth from the request when
middleware ran" currently only verifies the output shape (userId and tokenType)
but doesn't assert that the critical re-derivation path via
clerkClient().authenticateRequest() is actually being called. Add explicit call
assertions by mocking or spying on the clerkClient and its authenticateRequest
method, then verify with expect statements that authenticateRequest was invoked
with the expected request object. This ensures future refactors cannot
accidentally bypass the request-based re-authentication path.

In `@packages/react-router/src/server/clerkMiddleware.ts`:
- Around line 23-28: The exported symbols middlewareConfigContext and
resolveRequestState() appear to be internal implementation details but lack
proper JSDoc documentation. Since these symbols are exported from
packages/react-router/src and could surface in generated documentation or direct
imports, add `@internal` JSDoc comments above both middlewareConfigContext (the
createContext call) and the resolveRequestState function definition to mark them
as internal-only plumbing, preventing them from appearing in public-facing Clerk
documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 4e33398a-0c9e-4da0-873f-da63f8c6eaae

📥 Commits

Reviewing files that changed from the base of the PR and between 6a06393 and af213e9.

📒 Files selected for processing (4)
  • packages/react-router/src/server/__tests__/clerkMiddleware.authIsolation.test.ts
  • packages/react-router/src/server/__tests__/getAuth.test.ts
  • packages/react-router/src/server/__tests__/rootAuthLoader.test.ts
  • packages/react-router/src/server/clerkMiddleware.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-router/src/server/tests/rootAuthLoader.test.ts
  • packages/react-router/src/server/tests/clerkMiddleware.authIsolation.test.ts

Comment thread packages/react-router/src/server/clerkMiddleware.ts Outdated
@vercel vercel Bot temporarily deployed to Preview – clerk-js-sandbox June 17, 2026 20:01 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant